[转] Linux下使用PyInstaller打包Python程序

2015/03/26 17:40
阅读数 5.2K

1.Linux系统下安装pyinstaller

    # easy_install pyinstaller
    Adding PyInstaller 2.1 to easy-install.pth file
    Installing pyinstaller script to /usr/local/bin
    Installing pyi-grab_version script to /usr/local/bin
    Installing pyi-archive_viewer script to /usr/local/bin
    Installing pyi-build script to /usr/local/bin
    Installing pyi-make_comserver script to /usr/local/bin
    Installing pyi-bindepend script to /usr/local/bin
    Installing pyi-set_version script to /usr/local/bin
    Installing pyi-makespec script to /usr/local/bin

2.pyinstaller工具安装在/usr/local/bin下

    # which pyinstaller
    /usr/local/bin
    # ls /usr/local/bin|grep pyi*
    pyi-archive_viewer
    pyi-bindepend
    pyi-build
    pyi-grab_version
    pyi-make_comserver
    pyi-makespec
    pyinstaller
    pyi-set_version

3.创建测试程序main.py

    print('hello world!')

4.使用pyinstaller创建可执行程序

    # pyinstaller main.py

执行后生成相关目录和文件

    # tree .
    .
    ├── build
    │   └── main
    │       ├── logdict2.7.8.final.0-1.log
    │       ├── main
    │       ├── out00-Analysis.toc
    │       ├── out00-COLLECT.toc
    │       ├── out00-EXE.toc
    │       ├── out00-PKG.pkg
    │       ├── out00-PKG.toc
    │       ├── out00-PYZ.pyz
    │       ├── out00-PYZ.toc
    │       └── warnmain.txt
    ├── dist
    │   └── main
    │       ├── audioop.so
    │       ├── bz2.so
    │       ├── _codecs_cn.so
    │       ├── _codecs_hk.so
    │       ├── _codecs_iso2022.so
    │       ├── _codecs_jp.so
    │       ├── _codecs_kr.so
    │       ├── _codecs_tw.so
    │       ├── _hashlib.so
    │       ├── libbz2.so.1.0
    │       ├── libcrypto.so.1.0.0
    │       ├── libpython2.7.so.1.0
    │       ├── libreadline.so.6
    │       ├── libssl.so.1.0.0
    │       ├── libtinfo.so.5
    │       ├── libz.so.1
    │       ├── main
    │       ├── _multibytecodec.so
    │       ├── readline.so
    │       ├── _ssl.so
    │       └── termios.so
    ├── main.py
    └── main.spec
    4 directories, 33 files

生成了main.spec文件和build、dist两个文件夹,可执行程序位于目录./dist/main/,而该目录下所有程序均为运行时所需文件。

5.运行程序

    # ./dist/main/main
    hello world!

6.生成单个文件

清除main.py以外文件
# rm -rf ./build ./dist main.spec
生成单一执行文件
# pyinstaller -F main.py
执行后生成相关目录和文件

    # tree .
    .
    ├── build
    │   └── main
    │       ├── logdict2.7.8.final.0-1.log
    │       ├── out00-Analysis.toc
    │       ├── out00-EXE.toc
    │       ├── out00-PKG.pkg
    │       ├── out00-PKG.toc
    │       ├── out00-PYZ.pyz
    │       ├── out00-PYZ.toc
    │       └── warnmain.txt
    ├── dist
    │   └── main
    ├── main.py
    └── main.spec

    3 directories, 11 files

7.运行程序

    # ./dist/main/main
    hello world!

8.压缩程序

UPX使用一种叫做UCL的压缩算法,这是一个对有部分专有算法的NRV(Not Really Vanished)算法的一个开源实现。
debian下安装UPX方法如下:

    # apt-get install upx-ucl

确认upx路径

    # which upx
    /usr/bin/upx

使用压缩方式打包单个可执行程序。由于–upx参数已废弃,需要使用–upx-dir参数

    # pyinstaller -F --upx-dir=/usr/bin main.py
    # ll dist/main
    -rwxr-xr-x 1 albert albert 4229368 Dec  5 20:54 dist/main

对比未压缩时的体积

    # pyinstaller -F --noupx main.py
    # ll dist/main
    -rwxr-xr-x 1 albert albert 4306465 Dec  5 21:03 dist/main

当然pyinstaller默认会搜索PATH路径的,所以apt安装upx的话,pyinstaller能识别出来,并自动启用压缩。

    24 INFO: UPX is available.

不想压缩可以使用–noupx参数。

9.资源文件(生成文件夹模式)

当使用图片或者外部数据时,就需要把这些资源文件一同打包到程序中。还记得main.py同目录下生成的main.spec文件吗?这个文件就是配置文件。现看下需要外部资源的python程序

    # cat main.py
    with open('foo') as fi:
        print(data)
    # cat foo
    Hello World!

我们为了添加资源需要使用pyi-makespec生成配置文件,然后使用pyi-build生成文件夹模式的可执行程序。

    # pyi-makespec main.py
    wrote /home/albert/PycharmProjects/createonefile/main.spec
    now run pyinstaller.py to build the executable

现在看下spec配置文件的内容,万幸是个python格式的文件。

    # cat main.spec
    # -*- mode: python -*-
    a = Analysis(['main.py'],
                 pathex=['/home/albert/PycharmProjects/createonefile'],
                 hiddenimports=[],
                 hookspath=None,
                 runtime_hooks=None)
    pyz = PYZ(a.pure)
    exe = EXE(pyz,
              a.scripts,
              exclude_binaries=True,
              name='main',
              debug=False,
              strip=None,
              upx=True,
              console=True )
    coll = COLLECT(exe,
                   a.binaries,
                   a.zipfiles,
                   a.datas,
                   strip=None,
                   upx=True,
                   name='main')

需要添加资源需要使用TOC(Table of Contents)格式(name,path,typecode)
name:打包到程序文件夹内的资源文件名
path:打包前资源文件的绝对路径(包括文件名)
typecode:有3种

    typecode    description name    path
    'BINARY'    A shared library.   Run-time name.  Full path name in build.
    'DATA'  Arbitrary files.    Run-time name.  Full path name in build.
    'OPTION'    A Python run-time option.   Option code ignored.

那么需要添加spec的TOC便是
(‘foo’, ‘/home/albert/PycharmProjects/createonefile/foo’, ‘DATA’)
该TOC需要放到list中,然后作为参数传递给COLLECT函数,修改后的spec文件如下:

    # cat main.spec
    # -*- mode: python -*-
    a = Analysis(['main.py'],
                 pathex=['/home/albert/PycharmProjects/createonefile'],
                 hiddenimports=[],
                 hookspath=None,
                 runtime_hooks=None)
    pyz = PYZ(a.pure)
    exe = EXE(pyz,
              a.scripts,
              exclude_binaries=True,
              name='main',
              debug=False,
              strip=None,
              upx=True,
              console=True )
    coll = COLLECT(exe,
                   a.binaries,
                   a.zipfiles,
                   a.datas,

                   strip=None,
                   upx=True,
                   name='main')

最后一步就是执行pyi-build编译可执行文件

    # pyi-build main.spec
    # tree .
    .
    ├── build
    │   └── main
    │       ├── logdict2.7.8.final.0-1.log
    │       ├── main
    │       ├── out00-Analysis.toc
    │       ├── out00-COLLECT.toc
    │       ├── out00-EXE.toc
    │       ├── out00-PKG.pkg
    │       ├── out00-PKG.toc
    │       ├── out00-PYZ.pyz
    │       ├── out00-PYZ.toc
    │       └── warnmain.txt
    ├── dist
    │   └── main
    │       ├── audioop.so
    │       ├── bz2.so
    │       ├── _codecs_cn.so
    │       ├── _codecs_hk.so
    │       ├── _codecs_iso2022.so
    │       ├── _codecs_jp.so
    │       ├── _codecs_kr.so
    │       ├── _codecs_tw.so
    │       ├── foo
    │       ├── _hashlib.so
    │       ├── libbz2.so.1.0
    │       ├── libcrypto.so.1.0.0
    │       ├── libpython2.7.so.1.0
    │       ├── libreadline.so.6
    │       ├── libssl.so.1.0.0
    │       ├── libtinfo.so.5
    │       ├── libz.so.1
    │       ├── main
    │       ├── _multibytecodec.so
    │       ├── readline.so
    │       ├── _ssl.so
    │       └── termios.so
    ├── foo
    ├── main.py
    └── main.spec

    4 directories, 35 files

大家已经可以看到./dist/main/foo了吧。为了避免读取原文件,我们进入到./dist/main执行下main。

    # cd ./dist/main
    # ./main
    Hello World!

10.资源文件(生成单文件模式)

单文件模式包含外部资源就要麻烦许多了,运行程序的时候先将文件解压到sys._MEIPASS指向的目录下,所以调用资源文件就需要添加os.path.join(sys._MEIPASS,filename)。但打包前调试时sys又没有_MEIPASS属性,那就又要添加如下代码

    if getattr(sys, 'frozen', False):
        # we are running in a |PyInstaller| bundle
        basedir = sys._MEIPASS
    else:
        # we are running in a normal Python environment
        basedir = os.path.dirname(__file__)

这样调用资源文件时就使用os.path.join(basedir,filename)就可以了。我们先来看看修改后的main.py

    # cat main.py
    import sys
    import os

    if getattr(sys, 'frozen', False):
        # we are running in a |PyInstaller| bundle
        basedir = sys._MEIPASS
    else:
        # we are running in a normal Python environment
        basedir = os.path.dirname(__file__)

    with open(os.path.join(basedir,'foo')) as fi:
        print(fi.read())

然后生成spec文件,onefile模式下生成的spec与onedic模式是不一样的。

    # pyi-makespec -F main.py
    # -*- mode: python -*-
    a = Analysis(['main.py'],
                 pathex=['/home/albert/PycharmProjects/createonefile'],
                 hiddenimports=[],
                 hookspath=None,
                 runtime_hooks=None)

    pyz = PYZ(a.pure)
    exe = EXE(pyz,
              a.scripts,
              a.binaries,
              a.zipfiles,
              a.datas,
              name='main',
              debug=False,
              strip=None,
              upx=True,
              console=True )

需要把资源文件整理为TOC格式,添加到spec中,不同的是onedic模式是添加到COLLECT的参数中,onefile模式并不调用COLLECT函数,所以TOC是添加到EXE参数中。修改后的spec如下

    # cat main.spec
    # -*- mode: python -*-
    a = Analysis(['main.py'],
                 pathex=['/home/albert/PycharmProjects/createonefile'],
                 hiddenimports=[],
                 hookspath=None,
                 runtime_hooks=None)

    pyz = PYZ(a.pure)
    exe = EXE(pyz,
              a.scripts,
              a.binaries,
              a.zipfiles,
              a.datas,
              [('foo', '/home/albert/PycharmProjects/createonefile/foo', 'DATA')],
              name='main',
              debug=False,
              strip=None,
              upx=True,
              console=True )

然后编译测试

    # pyi-build main.spec
    # cd dist
    # ./main
    Hello World!

11.多资源文件(生成单文件模式)

多资源文件时,可以把多个TOC文件放到同一个列表中

    [('foo1', '/home/albert/PycharmProjects/createonefile/resource/foo1', 'DATA'),
     ('foo2', '/home/albert/PycharmProjects/createonefile/resource/foo2', 'DATA'),
     ('foo3', '/home/albert/PycharmProjects/createonefile/resource/foo3', 'DATA')]

也可以TOC的升级版Tree。Tree可以把一个目录下的文件自动生成TCO列表。
Tree(root, prefix=run-time-folder, excludes=match)
root是需要打包的资源文件夹,可以是绝对路径也可以是相对路径
prefix是运行时的文件夹名。代码中就需要使用os.path.join(basedir,prefixdir,’foo1′)
excludes是个列表,可有可无,用于排除不需要的文件或子文件夹。既可以是文件名、文件夹名,也可以使用通配符如*.ext
上边的TOC列表就可以这样表达
Tree(‘/home/albert/PycharmProjects/createonefile/resource’,’resource’)
代码中可这样调用

    basedir=os.path.join(basedir,'resource')
    foo1=open(os.path.join(basedir,'foo1'))
    foo2=open(os.path.join(basedir,'foo2'))
    foo3=open(os.path.join(basedir,'foo3'))

完整代码:

    # tree .
    .
    ├── main.py
    └── resource
        ├── foo1
        ├── foo2
        └── foo3

    1 directory, 4 files


    # cat main.py
    import sys
    import os

    if getattr(sys, 'frozen', False):
        # we are running in a |PyInstaller| bundle
        basedir = sys._MEIPASS
    else:
        # we are running in a normal Python environment
        basedir = os.path.dirname(__file__)

    resourcedir=os.path.join(basedir,'resource')
    for filename in ['foo1','foo2','foo3']:
        with open(os.path.join(resourcedir,filename)) as fi:
            print(fi.read())


    # pyi-makespec -F main.py
    # emacs -nw main.spec
    ***add Tree Object to main.spec file***


    # cat main.spec
    # -*- mode: python -*-
    a = Analysis(['main.py'],
                 pathex=['/home/albert/PycharmProjects/createonefile'],
                 hiddenimports=[],
                 hookspath=None,
                 runtime_hooks=None)
    pyz = PYZ(a.pure)
    exe = EXE(pyz,
              a.scripts,
              a.binaries,
              a.zipfiles,
              a.datas,
              Tree('./resource','resource'),
              name='main',
              debug=False,
              strip=None,
              upx=True,
              console=True )

运行生成的文件。

    # cd dist
    # ./main
    Hello foo1!
    Hello foo2!
    Hello foo3!

12.多资源文件(生成单文件夹模式)

真懒的写了,手动copy进去不就结了嘛。不嫌麻烦就参考9、10、11稍微想一想就有了。

13.参考

http://pythonhosted.org/PyInstaller/

14.附件

creatonefile.tar.zg


展开阅读全文
打赏
1
0 收藏
分享
加载中
更多评论
打赏
0 评论
0 收藏
1
分享
返回顶部
顶部