首页 诗词 字典 板报 句子 名言 友答 励志 学校 网站地图
当前位置: 首页 > 教程频道 > 开发语言 > perl python >

py2exe+inno setup集成封装python程序

2013-03-21 
py2exe+inno setup集成打包python程序版权所有,转载请注明出处:http://guangboo.org/2013/03/16/build-pac

py2exe+inno setup集成打包python程序

版权所有,转载请注明出处:http://guangboo.org/2013/03/16/build-package-with-py2exe-inno-setup

在使用python开发windows程序时,我们都会对程序进行打包,而对于使用python语言编写的windows程序,包括窗体程序和控制台程序,通常使用py2exe或pyinstaller来进行打包。由于我没有使用过pyinstaller,因此本文所使用的打包库或工具是py2exe。然而,这个打包过程只是将诸多python文件打包成一个.exe文件,并且将python运行环境,及必要的python库一并打包,这样的打包过程会生成一个.exe文件(默认情况还会有一个w9xpopen.exe文件,该文件是为win98系统使用的),和python27.dl(根据所使用Python版本,文件名会有所不同),很多.pyd文件,及其他文件。这样打包的结果不是我们想象中的windows下的安装文件,本文结合使用py2exe和inno setup,对Python编写的windows程序打包成安装文件。

py2exe打包

由于本文示例代码是基于Python 2.7版本开发的,因此需要将VC2008的运行时打包进去,因为python 2.7是基于VC 2008编译的,可以参考:http://www.py2exe.org/index.cgi/Tutorial#Step521。本文示例打包的程序是使用wxPython开发的windows窗体程序,因此,我们的打包需要将wx库添加进来,并且程序不考虑windows98系统,因此希望把w9xponen.exe文件排除掉,并希望将python基础的类库打包进一个shared.zip文件中。setup.py示例代码如下:

inno setup打包

windows下有很多打包工具,如setup factory, inno setup等,这两种方式我都使用过,但是inno setup相对根据容易一下,只需要一个.iss文件即可实现打包过程,并且.iss文件的格式和.ini文件非常相似,并且可以在.iss文件中直接编写打包逻辑,其编写脚本是使用Pascal语言进行编写的。如下为简单的打包脚本:

另外,py2exe的版本我们可以通过变量VERSION来定义,在inno setup脚本中,我们发现版本号是写死的,这样对于inno setup打包就比较麻烦,每次版本变化就要编写一个新的脚本文件,虽然内容只有版本号不同。因此我们也希望能有一个变量,像py2exe打包一样有一个VERSION变量来定义。

py2exe, inno setup是否可以集成

一方面由于版本的变化会带来inno setup打包要编写新的脚本文件的麻烦,另一方面,这两个打包过程还是分开的,打包必须先执行setup脚本,使用py2exe生成可执行文件,然后在编写iss脚本,使用inno setup compiler来打包成安装文件。这个的过程本身也很麻烦,每次版本的变化都要这么麻烦的打包过程,可能觉得麻烦。理想的方案就是,执行一个setup.py脚本即可将两个打包过程都完成。

其实这个过程也不难实现,因为py2exe库提供了一个接口,允许我们添加自己的打包过程,或在打包完成后执行自定义的功能。我们的需求就是在py2exe打包完成后,自动根据当前版本号生成iss脚本,并且使用inno setup compiler来执行新生成的iss脚本文件。py2exe提供了这样的接口:

from py2exe.build_exe import py2exefrom distutils.dir_util import remove_treeclass build_installer(py2exe):    # This class first builds the exe file(s), then creates a Windows installer.    # You need InnoSetup for it.    def run(self):        # First, let py2exe do it's work.        py2exe.run(self)        # your own business codes.

上面的代码是定义了一个新的打包过程的类,继承了默认的py2exe类,我们可以在py2exe.run(self)代码后添加自己的代码,如删除py2exe打包过程中生成的build临时目录,根据当前版本生成iss脚本,并调用inno setup compiler执行该脚本,生成安装文件。

py2exe提供了扩展接口,那么现在的问题就是inno setup compiler进程的是否支持启动参数呢,即是否可以传递一个iss文件地址给该进程执行。当然有,inno setup compiler支持这样的参数,其格式是:

compil32.exe /cc 'iss file name'

现在问题都得到答案了,py2exe和inno setup两个打包过程是可以集成的,那么下面就是怎么集成的问题了。

自动生成ISS文件

集成的方案已经定下来了,并且可能遇到的问题也已经有了方案。下面还有一个工作没有做,就是根据当前版本生成iss脚本文件的过程。这个过程其实简单,因为有了上面的脚本示例,每次生成的脚本只有版本号不同而已,其他都可以直接输出。需要注意与版本相关的目录名和文件名。如下给出的示例代码:

# -*- coding:utf-8 -*-import appimport osclass InnoScript:    def __init__(self, output, input):        self._output = os.path.join(output, app.VERSION_TEXT)        self._input = input        self._script_file = os.path.join(self._output, 'setupscript-%s.iss' % app.VERSION_TEXT)        self._exename = 'app.exe'    def _create_script_file(self):        scf = os.path.dirname(self._script_file)        exec_path = os.path.abspath(os.path.dirname(__file__))                if not os.path.exists(scf):            os.mkdir(scf)                    f = open(self._script_file, 'w')        print >> f, "[Setup]"        print >> f, "AppId={{75BB853E-503D-416A-873E-844CDBB95B22}"        print >> f, "AppName=%s" % app.NAME        print >> f, "AppVersion=%s" % app.VERSION_TEXT        print >> f, "AppVerName=%s(%s)" % (app.NAME, app.VERSION_TEXT)        print >> f, "VersionInfoDescription=%s" % app.COMPANY        print >> f, "VersionInfoProductName=%s" % app.NAME        print >> f, "VersionInfoProductVersion=%s" % app.VERSION_TEXT        print >> f, "VersionInfoVersion=%s" % app.VERSION_TEXT        print >> f, "VersionInfoTextVersion=%s %s" % (app.VERSION_TEXT, 'Alpha')        print >> f, "VersionInfoCompany=%s" % app.COMPANY        print >> f, "VersionInfoCopyright=%s" % app.COPYRIGHT                print >> f, "AppPublisher=%s" % app.COMPANY        print >> f, "AppCopyright=%s" % app.COPYRIGHT        print >> f, "AppPublisherURL=%s" % app.COMPANY_SITE        print >> f, "DefaultDirName={pf}\XXX/test app"        print >> f, "DefaultGroupName=%s" % app.NAME        print >> f, "LicenseFile=%s" % app.LICENSE_FILE        print >> f, "OutputDir=%s" % self._output        print >> f, "SetupIconFile=%s" % os.path.join(exec_path, 'app.ico')        print >> f, "OutputBaseFilename=setup-%s" % app.VERSION_TEXT        print >> f, "Compression=lzma"        print >> f, "SolidCompression=yes"        print >> f, ""        print >> f, "[Languages]"        print >> f, 'Name: "english"; MessagesFile: "compiler:Default.isl"'        print >> f, ""        print >> f, "[Tasks]"        print >> f, 'Name: desktopicon; Description: "{cm:CreateDesktopIcon}"; GroupDescription: "{cm:AdditionalIcons}"; Flags: checkablealone'        print >> f, 'Name: quicklaunchicon; Description: "{cm:CreateQuickLaunchIcon}"; GroupDescription: "{cm:AdditionalIcons}"; Flags: unchecked'        print >> f, ""        print >> f, "[Files]"        print >> f, 'Source: "../vcredist_x86.exe"; DestDir:"{tmp}"; Check:NeedInstallVC9'        print >> f, 'Source: "%s\%s"; DestDir: "{app}"; Flags: ignoreversion' % (self._input, self._exename)        print >> f, 'Source: "%s\python27.dll"; DestDir: "{app}"; Flags: ignoreversion' % self._input        print >> f, 'Source: "%s\app.ico"; DestDir: "{app}"; Flags: ignoreversion' % self._input        print >> f, 'Source: "%s\lib\*"; DestDir: "{app}/lib"; Flags: ignoreversion recursesubdirs createallsubdirs' % self._input        print >> f, ""        print >> f, "[Icons]"        print >> f, 'Name: "{group}\%s"; Filename: "{app}\app.exe"' % app.NAME        print >> f, 'Name: "{group}\{cm:ProgramOnTheWeb,%s}"; Filename: "%s"' % (app.NAME, app.COMPANY_SITE)        print >> f, 'Name: "{userappdata}\Microsoft\Internet Explorer\Quick Launch\%s"; Filename: "{app}\%s"; Tasks: quicklaunchicon' % (app.NAME, self._exename)        print >> f, ""        print >> f, "[Run]"        print >> f, 'Filename: "{tmp}/vcredist_x86.exe"; Parameters: /q; WorkingDir: {tmp}; Flags: skipifdoesntexist; StatusMsg: "Installing Microsoft Visual C++ Runtime ..."; Check: NeedInstallVC9'        print >> f, '''Filename: "{app}\%s"; Description: "{cm:LaunchProgram,%s}"; Flags: nowait postinstall skipifsilent''' % (self._exename, self._exename)        print >> f, ""        print >> f, '''[Code]var vc9Missing: Boolean; function NeedInstallVC9(): Boolean;begin  Result := vc9Missing;end; function InitializeSetup(): Boolean;var version: Cardinal;begin  if RegQueryDWordValue(HKLM, 'SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\{FF66E9F6-83E7-3A3E-AF14-8DE9A809A6A4}', 'Version', version) = false then begin    vc9Missing := true;  end;  result := true;end;'''        f.close()            def compile(self):        self._create_script_file()        import subprocess        proc = subprocess.Popen('compil32.exe /cc "%s"' % self._script_file)        proc.wait()

上面的代码中有一个app模块,该模块定义了一些常量,主要有COMPANY, NAME等。该类的compile方法用调用了_create_script_file来生成iss脚本文件,然后调用inno setup的编译器compil32.exe,来编译刚生成的iss脚本。

打包集成

现在一切准备工作都做好了,下面一步就是将这些过程集成起来,使一次执行可以生成可执行文件和安装文件。前面介绍了py2exe的扩展接口,下面我们就将自定义的代码添加进去,代码如下:

from py2exe.build_exe import py2exefrom distutils.dir_util import remove_treeclass build_installer(py2exe):    # This class first builds the exe file(s), then creates a Windows installer.    # You need InnoSetup for it.    def run(self):        # First, let py2exe do it's work.        py2exe.run(self)        remove_tree(os.path.join(cw, 'build'))        setup_dir = os.path.join(self.dist_dir, '../../setup')        import inno_script        script = inno_script.InnoScript(setup_dir, self.dist_dir)        script.compile()

相比之前的代码,我们添加了删除py2exe打包过程生成的build临时目录的代码和生成安装文件的代码(包含生成iss脚本和编译两个过程)。然而只有新的py2exe类的定义还不够,需要在setup方法中使用它才行。使用方法也非常简单,只要在setup方法中添加一个cmdclass参数,其值为{"py2exe": build_installer},即可。这里只贴出setup方法的后半部分代码,前半部分和之前一样:

setup(    ...,    windows = [{'script':'app.py',                'icon_resources':[(1, 'icons\\app.ico')],                'copyright':'guangboo49@gmail.com',                'company_name':'Jeff zhang',                'name':'test name',                'version':VERSION + '.' + str(build)}],    cmdclass = {"py2exe": build_installer},)

另外,如果找不到compil32.exe命令时,请确认正确安装了inno setup,并且将inno setup根目录添加到PATH环境变量中。

总结

本文是为在使用python编写windows程序时,对繁琐的打包过程提出的一种解决方案,以便简化打包过程,使专注于程序开发,而不是打包。本文中的打包过程与版本管理有一定的关联,本文中的版本号保存在app.py模块,VERSION常量中,如版本发生变化,可以修改该值,然后重新执行setup.py脚本。

热点排行