5/09/2019

pyinstaller (Python Package)

1. pyinstaller 란? 

python으로 작성된 프로그램을 Package 단위로 배포의 편의성을 위해서 사용하는 것 같다.
아래의 사이트에서 글을 읽어보면  목적을 호환성으로 두고있다.

Python은 C Library로 사용이 가능하고, Python 의 내부 Library 가능하기때문에 Python으로만 개발을 진행을 했을 때, 이것을 다른곳에 배포할때 쉽지 않다.
이를 해결해주는 것이 아래의 Tool 이고 실행파일을 만들어준다.

  https://www.pyinstaller.org/

1.1 설치방법 

$ pip install pyinstaller
$ pip list | grep PyInstaller
PyInstaller (3.4)

$ pyinstaller --help
or 
$ ~/.local/bin/pyinstaller --help
usage: pyinstaller [-h] [-v] [-D] [-F] [--specpath DIR] [-n NAME]
                   [--add-data ]
                   [--add-binary ] [-p DIR]
                   [--hidden-import MODULENAME]
                   [--additional-hooks-dir HOOKSPATH]
                   [--runtime-hook RUNTIME_HOOKS] [--exclude-module EXCLUDES]
                   [--key KEY] [-d [{all,imports,bootloader,noarchive}]] [-s]
                   [--noupx] [-c] [-w]
                   [-i ]
                   [--version-file FILE] [-m ] [-r RESOURCE]
                   [--uac-admin] [--uac-uiaccess] [--win-private-assemblies]
                   [--win-no-prefer-redirects]
                   [--osx-bundle-identifier BUNDLE_IDENTIFIER]
                   [--runtime-tmpdir PATH] [--bootloader-ignore-signals]
                   [--distpath DIR] [--workpath WORKPATH] [-y]
                   [--upx-dir UPX_DIR] [-a] [--clean] [--log-level LEVEL]
                   scriptname [scriptname ...]

$ pyinstaller --noconfirm --log-level=WARN \
    --onefile --nowindow \
    --add-data="README:." \
    --add-data="image1.png:img" \               
    --add-binary="libfoo.so:lib" \                  //library 를 추가시  세부 옵션은 아래의 사이트에서 확인 , 사용시, *.spec파일도 변경 
    --hidden-import=secret1 \
    --hidden-import=secret2 \
    --upx-dir=/usr/local/share/ \
    myscript.spec


설치방법
  https://pypi.org/project/PyInstaller/

사용방법- 자세히 읽어보자
  https://pyinstaller.readthedocs.io/en/stable/usage.html

1.2 기본테스트 

간단하게  실행가능한 python을 작성해보고, 이와 관련된 부분들을 Package를 만드는 작업을 해보자.
  • TEST python 작성
$ pip install procfs
$ vi test.py
import io
from procfs import Proc

def JHLEE():
    print "Jeonghun LEE"


if __name__ == "__main__":

   JHLEE()
   proc = Proc()
   print proc.loadavg

$ python test.py
Jeonghun LEE
{'average': {1: 0.08, 5: 0.09, 15: 0.07},
 'entities': {'current': 1, 'total': 423},
 'last_pid': 3833}


  • Package 테스트 
$ pyinstaller test.py
or 
$ ~/.local/bin/pyinstaller test.py  // 실행할 python 
64 INFO: PyInstaller: 3.4
65 INFO: Python: 2.7.12
65 INFO: Platform: Linux-4.4.38-tegra-aarch64-with-Ubuntu-16.04-xenial
66 INFO: wrote /home/nvidia/jhlee/pyinstall/test.spec
90 INFO: UPX is not available.
94 INFO: Extending PYTHONPATH with paths
['/home/nvidia/jhlee/pyinstall', '/home/nvidia/jhlee/pyinstall']
94 INFO: checking Analysis
106 INFO: checking PYZ
112 INFO: checking PKG
113 INFO: Bootloader /home/nvidia/.local/lib/python2.7/site-packages/PyInstaller/bootloader/Linux-64bit-aarch/run
113 INFO: checking EXE
114 INFO: checking COLLECT
WARNING: The output directory "/home/nvidia/jhlee/pyinstall/dist/test" and ALL ITS CONTENTS will be REMOVED! Continue? (y/n)y
5285 INFO: Removing dir /home/nvidia/jhlee/pyinstall/dist/test
5312 INFO: Building COLLECT COLLECT-00.toc
5357 INFO: Building COLLECT COLLECT-00.toc completed successfully.

$ cat test.spec  //pyinstaller 상위 실행으로 생성 
# -*- mode: python -*-

block_cipher = None


a = Analysis(['test.py'],
             pathex=['/home/nvidia/jhlee/pyinstall'],
             binaries=[],                        //binaries=[('foo.so', 'lib')],  직접 추가해도 됨 
             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='test',
          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='test')


  • 생성된 Package 부분 확인 및 실행 

$ ls dist/test/
bz2.aarch64-linux-gnu.so              _codecs_tw.aarch64-linux-gnu.so       libtinfo.so.5
_codecs_cn.aarch64-linux-gnu.so       _hashlib.aarch64-linux-gnu.so         libz.so.1
_codecs_hk.aarch64-linux-gnu.so       libbz2.so.1.0                         _multibytecodec.aarch64-linux-gnu.so
_codecs_iso2022.aarch64-linux-gnu.so  libcrypto.so.1.0.0                    readline.aarch64-linux-gnu.so
_codecs_jp.aarch64-linux-gnu.so       libpython2.7.so.1.0                   resource.aarch64-linux-gnu.so
_codecs_kr.aarch64-linux-gnu.so       libreadline.so.6                      test

$  dist/test/test   //ELF 실행 완료 
Jeonghun LEE
'1.10 1.06 1.03 1/554 11741\n'


  • spec 파일로 Package 생성 
위에서 Package 만드는것을 다시 반복한다면, 아래와 같이 spec파일로만 다시 실행

$ pyinstaller test.py
or  
$ ~/.local/bin/pyinstaller test.spec 
64 INFO: PyInstaller: 3.4
65 INFO: Python: 2.7.12
65 INFO: Platform: Linux-4.4.38-tegra-aarch64-with-Ubuntu-16.04-xenial
88 INFO: UPX is not available.
93 INFO: Extending PYTHONPATH with paths
['/home/nvidia/jhlee/pyinstall', '/home/nvidia/jhlee/pyinstall']
93 INFO: checking Analysis
100 INFO: Building because inputs changed
101 INFO: Initializing module dependency graph...
105 INFO: Initializing module graph hooks...
265 INFO: running Analysis Analysis-00.toc
369 INFO: Caching module hooks...
403 INFO: Analyzing test.py
3816 INFO: Loading module hooks...
3818 INFO: Loading module hook "hook-encodings.py"...
5360 INFO: Looking for ctypes DLLs
5360 INFO: Analyzing run-time hooks ...
5371 INFO: Looking for dynamic libraries
6439 INFO: Looking for eggs
6440 INFO: Python library not in binary dependencies. Doing additional searching...
6724 INFO: Using Python library /usr/lib/aarch64-linux-gnu/libpython2.7.so.1.0
6741 INFO: Warnings written to /home/nvidia/jhlee/pyinstall/build/test/warn-test.txt
6799 INFO: Graph cross-reference written to /home/nvidia/jhlee/pyinstall/build/test/xref-test.html
6908 INFO: checking PYZ
6918 INFO: checking PKG
6919 INFO: Bootloader /home/nvidia/.local/lib/python2.7/site-packages/PyInstaller/bootloader/Linux-64bit-aarch/run
6919 INFO: checking EXE
6921 INFO: checking COLLECT
WARNING: The output directory "/home/nvidia/jhlee/pyinstall/dist/test" and ALL ITS CONTENTS will be REMOVED! Continue? (y/n)y
12143 INFO: Removing dir /home/nvidia/jhlee/pyinstall/dist/test
12167 INFO: Building COLLECT COLLECT-00.toc
12214 INFO: Building COLLECT COLLECT-00.toc completed successfully. 



1.3 Spec 파일 수정 및 옵션 

상위에서 처럼 실행하면 spec파일은 자동으로 생성이 되며, 사용되는 옵션에따라 spec파일도 변경이 된다.

제대로 생성이 안되거나 동작이 안되면, WARNING 과  build/warn-xxxx 파일을 파악하여 분석하여 상위 옵션을 변경하거나, spec을 직접 수정하자.

PYTHONPATH  (-p)

pyinstaller setup.spec
    https://pythonhosted.org/PyInstaller/spec-files.html

2. pyinstaller 사용후 결론  

회사동료가 추천을 해주어 Python Project를  이 프로그램을 이용하여 쉽게 Package를 만들고, 이것이 ELF 포맷으로 동작되는 것을 보고,
Python의 Package가 완전 Compile되어 동작되는 줄 잠시 착각했다.

하지만 동작방식을 세부적으로 분석을 해 보면, Python을 Compile되는 방식이 아니라 ELF 포맷으로 맞춰주고 안에 구성을 Python library을 넣어
나머지 의 기본틀로 동작시켜 구동하는 구조이다.
(확인 방법은 간단하며, ELF Format을 간단히 분석을 해보면 된다 ldd 와 readelf )

결론적으로 Python의 성능상의 개선은 있겠지만, 다만 관련 Library를 한곳에 넣어 배포의 편이성 및 호환성에 맞추어 야 할 것 같다. 

나중에는 완전히 Library 형태로 Python의 종속을 벗어난 형태로 ELF 포맷으로 변경이 될지도 모르니
그때 다시 한번테스트를 한번해봐야 할 것 같다.