- 浏览: 1843316 次
文章分类
最新评论
-
coosummer:
推荐使用http://buttoncssgenerator.c ...
CSS控制<a>标签变为button -
Allen_J_Will:
哥们,事情没有你说的那么简单,很大的一个项目中,依赖jar包的 ...
struts中java.lang.NoClassDefFoundError: com/opensymphony/xwork2/util/TextUtils的解决办法
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示例代码如下:
import sys import os from glob import glob from distutils.core import setup import py2exe import app includes = ['wx', 'wx.html', 'select', 'hashlib'] excludes = ['bz2', 'unicodedata'] packages = [] dll_excludes = ['w9xpopen.exe'] VERSION = app.VERSION cw = os.path.dirname(__file__) data_files = [('', ['License.txt']), ('', [os.path.join(cw, 'icons/app.ico')]), ('lib', glob(r'C:\Program Files\Microsoft Visual Studio 9.0\VC\redist\x86\Microsoft.VC90.CRT\*.*'))] sys.path.append("C:\\Program Files\\Microsoft Visual Studio 9.0\\VC\\redist\\x86\\Microsoft.VC90.CRT") try: build_file = open('../dist/build.txt', 'r') build = int(build_file.readline()) except: build = 0 setup( version = VERSION+ '.' + str(build), name = 'test app', description = 'app for test.', long_description = '', author = 'Jeff Zhang', author_email = 'guangboo49@gmail.com', data_files = data_files, options = {'py2exe':{'compressed':1, 'optimize':2, 'includes':includes, 'excludes':excludes, 'packages':packages, 'dll_excludes':dll_excludes, 'bundle_files':3, 'dist_dir':os.path.join('../dist/',VERSION), 'xref':False, 'skip_archive':False, 'ascii':False, 'custom_boot_script':'', } }, zipfile = 'lib/shared.zip', 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)}], ) open('../dist/build.txt', 'w').write(str(build + 1))
示例中,build是用来作为版本号的最后一位,表示生成的编号,该编号一直往上涨,不予主版本和次版本号的变化而清零。build版本号保存在一个build.txt文件中,每次执行该脚本时都会提前文件中的数字作为build版本号,打包完后,将加一后的新build版本号再保存到build.txt文件中。setup函数制定了一个zipfile参数,其值为lib/shared.zip,表示将Python基础库打包到lib/shared.zip文件中。另外需要注意到是windows参数中icon_resources,因为该参数涉及到的ico文件如果设置不正确,可能在windows 7等系统中显示出现问题,可以参考,之前专门的文章提供的解决方案:http://guangboo.org/2013/01/10/exe-file-packaging-with-py2exe-cant-display-ico-in-vista-win7。
以上脚本是将python程序打包成.exe可执行文件(包括其他dll和pyd等文件),上面的脚本生成的结果,如下图目录结构:
inno setup打包
windows下有很多打包工具,如setup factory, inno setup等,这两种方式我都使用过,但是inno setup相对根据容易一下,只需要一个.iss文件即可实现打包过程,并且.iss文件的格式和.ini文件非常相似,并且可以在.iss文件中直接编写打包逻辑,其编写脚本是使用Pascal语言进行编写的。如下为简单的打包脚本:
[Setup] AppId={{75BB8432-50eD-436A-873E-844CDC495B22} AppName=test app AppVersion=0.17.11 AppVerName=test app(0.17.11) VersionInfoDescription=XXX Co.,Ltd. VersionInfoProductName=test app VersionInfoProductVersion=0.17.11 VersionInfoVersion=0.17.11 VersionInfoTextVersion=0.17.11 Alpha VersionInfoCompany=XXX Co.,Ltd. VersionInfoCopyright=Copyright (C) XXX Co.,Ltd. All right reserved. AppPublisher=XXX Co.,Ltd. AppCopyright=Copyright (C) XXX Co.,Ltd. All right reserved. AppPublisherURL=http://www.xxxx.net DefaultDirName={pf}\test app DefaultGroupName=test app LicenseFile= OutputDir=D:\setup\0.17.11 SetupIconFile=D:\src\app.ico OutputBaseFilename=setup-0.17.11 Compression=lzma SolidCompression=yes [Languages] Name: "english"; MessagesFile: "compiler:Default.isl" [Tasks] Name: desktopicon; Description: "{cm:CreateDesktopIcon}"; GroupDescription: "{cm:AdditionalIcons}"; Flags: checkablealone Name: quicklaunchicon; Description: "{cm:CreateQuickLaunchIcon}"; GroupDescription: "{cm:AdditionalIcons}"; Flags: unchecked [Files] Source: "../vcredist_x86.exe"; DestDir:"{tmp}"; Check:NeedInstallVC9 Source: "D:\dist\0.17.11\app.exe"; DestDir: "{app}"; Flags: ignoreversion Source: "D:\dist\0.17.11\python27.dll"; DestDir: "{app}"; Flags: ignoreversion Source: "D:\dist\0.17.11\app.ico"; DestDir: "{app}"; Flags: ignoreversion Source: "D:\dist\0.17.11\lib\*"; DestDir: "{app}/lib"; Flags: ignoreversion recursesubdirs createallsubdirs [Icons] Name: "{group}\test app"; Filename: "{app}\app.exe" Name: "{group}\{cm:ProgramOnTheWeb, test app}"; Filename: "http://www.xxxx.net" Name: "{userappdata}\Microsoft\Internet Explorer\Quick Launch\test app"; Filename: "{app}\app.exe"; Tasks: quicklaunchicon [Run] Filename: "{tmp}/vcredist_x86.exe"; Parameters: /q; WorkingDir: {tmp}; Flags: skipifdoesntexist; StatusMsg: "Installing Microsoft Visual C++ Runtime ..."; Check: NeedInstallVC9 Filename: "{app}\app.exe"; Description: "{cm:LaunchProgram,app.exe}"; Flags: nowait postinstall skipifsilent [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;
其中[Code]节定义了一个函数,用于判断是否需要安装VC9运行时。因为我在使用inno setup打包时也把VC9运行时打包进去了,如果客户端没有安装VC9运行时的话,inno setup就会调用VC9的安装包vcredist_x86.exe进行安装。[Files]节表示要打包的文件,第一行包含了vcredist_x86.exe文件。
版本控制
因为我们每次打包时可能会有python代码的修改,有代码修改就有版本的变化,前面py2exe打包时,我们使用build版本号,来表示生成或打包的变化。然而这个版本号一般不是主要的,主要的变化在于主版本和次版本,有时还包括修正版本。为了区别和记录软件打包的版本变化,本文主要区分主版本,次版本和修正版本,当版本号的这三个部分发生变化时,都将生成新的目录,如0.7.11表示0.7.11版本打包的目录,0.7.12表示下一个修正版本的目录。
由于我们采用的是先使用py2exe进行打包生成可执行文件,然后在使用inno setup进行打包生成安装文件。并且py2exe输出目录在dist目录,inno setup的输出目录是setup目录,目录结构如图:
另外,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 py2exe from distutils.dir_util import remove_tree class 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 app import os class 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 py2exe from distutils.dir_util import remove_tree class 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脚本。
相关推荐
exe4j+++Inno+Setup打包工具能够对web应用进行打包成.exe安装文件,用户使用方便 不用派遣技术支持去用户那里安装配置环境了,非常好的打包工具,希望对有需要的朋友能够有所帮助
Inno Setup集成mysql和JDK,将jar包打包成exe,里面有mysql初始化脚本和Java项目启动等脚本,可以直接使用,非常全面。
使用inno setup将python exe制作安装包详细教程(csdn)————程序
Inno Setup是一个为Microsoft Windows应用程序创建安装程序的工具。innoextract允许在非Windows系统下提取此类安装程序,而无需使用wine运行实际的安装程序。innoextract 目前支持由 Inno Setup 1.2.10 到 6.2.1 ...
使用innosetup打包mysql,制作安装文件!共享所有代码!
springboot项目可以通过exe4j生成exe文件运行在windows系统上,并使用innosetup进一步包装exe文件和jre,生成可安装的exe文件,压缩文件大小
exe4j + Inno Setup安装文件及步骤。一键搞定Java桌面应用安装部署.有详细的说明步骤,轻松搞定把jar文件打包成exe文件。
Inno Setup 6打包后安装程序改为中文教程
Inno Setup 5.5.9(a) 打包软件实现打包Python3.x+Pyqt5+Pyinstaller生成的*.exe可执行文件。文档包括:1)Inno Setup 5.5.9(a)中文安装程序;2)Python3.x+Pyqt5+Pyinstaller生成的*.exe可执行文件及系列文件(main...
innosetup 是一个免费的 Windows 安装程序制作软件,可以通过该工具完成对exe程序的打包,生成安装程序
innosetup打包程序 参考教程:http://blog.csdn.net/itas109
可解加密的inno setup安装程序,可解加密的inno setup安装程序
inno setup java 打包工具 inno setup java 打包工具
innosetup;支持VB程序打包;VC程序打包。很容易对程序进行打包,而且功能强大。
Inno Setup 集成版 免费无版权争议的打包软件,安全无毒好用
Inno Setup是一款免费的安装程序制作工具,用于创建Windows平台下的安装程序。它具有易于使用、功能强大、灵活性高等特点,是Windows安装程序制作的首选工具之一。 Inno Setup使用Pascal脚本语言编写,因此对于...
InnoSetup制作打包工具,可以自己定制制作安装包工具
一个Python innosetup库 要求 Python 2.5或更高版本 特征 您可以使用自定义的InnoSetup脚本。 通过setup()元数据的安装程序元数据 从setup()元数据生成AppId(GUID)请...import py2exe, innosetup # All optio
使用Inno Setup5.5的集成打包 其中包括JDK1.8(配置环境变量) Tomcat8 Mysql5.7 Navicat12及注册机
通过Inno Setup把写好的C#应用程序打包成安装程序,在安装时安装程序会自动的检索本机是否存在.net Framework 环境,如果不存在则安装环境,存在不会安装。