在上一节PyGobject(一百一十)代码整合及GtkSource安装使用中,对代码进行了整合,在自己电脑上或者别人安装好同样环境的电脑上才可以运行,如何将代码打包成App或者exe等可独立运行的程序,在没有装过Python和PyGobject上的电脑上运行,就显得比较重要,本文就是基于这个原因而诞生的。
首先我试过py2app,这个打包一般的应用比较好用,可惜的是目前还不支持PyGobject的打包,遂放弃了。不知py2exe能不能行。
接着尝试使用Pyinstaller,官网上介绍说支持PyGobject。接下来详细介绍~
安装比较简单
pip3 install pyinstaller
cd /Applications/Project/Python/project/PYGUI/pygtk3
pyinstaller --onedir -y -w gtk-demo.py
当然,没有那么简单,打包过程中会有很多错误,gtk-demo.app打不开,打开dist/gtk-demo目录下的gtk-demo,报错
/Applications/Project/Python/project/PYGUI/pygtk3/dist/gtk-demo/gi/module.py:178: Warning: cannot register existing type ‘gchar’
**
GLib-GObject:ERROR:gvaluetypes.c:455:_g_value_types_init: assertion failed: (type == G_TYPE_CHAR)
Abort trap: 6
我们还需要做一些工作,见下
现在直接使用Pyinstaller打包我的代码还有点问题,因为我有用到GtkSource,但是它没有添加到Pyinstaller的hooks中
# -----------------------------------------------------------------------------
# Copyright (c) 2005-2016, PyInstaller Development Team.
#
# Distributed under the terms of the GNU General Public License with exception
# for distributing bootloader.
#
# The full license is in the file COPYING.txt, distributed with this software.
# -----------------------------------------------------------------------------
"""
Import hook for PyGObject https://wiki.gnome.org/PyGObject
"""
from PyInstaller.utils.hooks import get_gi_typelibs, collect_glib_share_files
binaries, datas, hiddenimports = get_gi_typelibs('GtkSource', '3.0')
datas += collect_glib_share_files('gtksourceview-3.0')
#-----------------------------------------------------------------------------
# Copyright (c) 2005-2016, PyInstaller Development Team.
#
# Distributed under the terms of the GNU General Public License with exception
# for distributing bootloader.
#
# The full license is in the file COPYING.txt, distributed with this software.
#-----------------------------------------------------------------------------
def pre_safe_import_module(api):
# PyGObject modules loaded through the gi repository are marked as
# MissingModules by modulegraph so we convert them to
# RuntimeModules so their hooks are loaded and run.
api.add_runtime_module(api.module_name)
hiddenimports += ["cairo"]
datas += collect_glib_share_files('fontconfig')
datas += collect_glib_share_files('icons')
datas += collect_glib_share_files('themes')
datas += collect_glib_translations('gtk30')
datas += collect_glib_share_files('mime')
打包的时候有报错
Unable to find gir directory: /share/gir-1.0.
解决办法:
将$USER/gtk/inst/share/gir-1.0 复制到/share 目录下
Pyinstaller打包貌似对软连接的支持不太好,将
/usr/share/icon;/usr/share/themes;/usr/share/gtksourceview-3.0
中的软连接全部删掉,将$USER/gtk/inst/share目录下的相关内容拷贝到/usr/share对应的文件下
pyinstaller --onedir -y -w gtk-demo.py
生成的gtk-demo.app仍然打不开,打开dist/gtk-demo目录下的gtk-demo,报错
GLib.Error: g-file-error-quark:
打开文件“/Applications/Project/Python/project/PYGUI/pygtk3/dist/gtk-demo/demos/Data/demo.gresource”失败:open() 失败:No such file or directory
看起来是文件没有打包进应用程序
运行上面的命令会生成一个gtk-demo.spec文件,
开始它是这样的
# -*- mode: python -*-
block_cipher = None
a = Analysis(['gtk-demo.py'],
pathex=['/Applications/Project/Python/project/PYGUI/pygtk3'],
binaries=None,
datas=None,
hiddenimports=[],
hookspath=[],
runtime_hooks=[],
excludes=[],
win_no_prefer_redirects=False,
win_private_assemblies=False,
cipher=block_cipher)
pyz = PYZ(a.pure, a.zipped_data,
cipher=block_cipher)
exe = EXE(pyz,
a.scripts,
exclude_binaries=True,
name='gtk-demo',
debug=False,
strip=False,
upx=True,
console=False )
coll = COLLECT(exe,
a.binaries,
a.zipfiles,
a.datas,
strip=False,
upx=True,
name='gtk-demo')
app = BUNDLE(coll,
name='gtk-demo.app',
icon=None,
bundle_identifier=None)
现在我们需要对它做一点修改
# -*- mode: python -*-
block_cipher = None
APP_NAME='gtk-demo'
added_files = [( 'demos', '' ),]
binaries_files=[]
a = Analysis(['gtk-demo.py'],
pathex=['/Applications/Project/Python/project/PYGUI/pygtk3'],
binaries=binaries_files,
datas=added_files,
hiddenimports=["pygtkcompat"],
hookspath=[],
runtime_hooks=[],
excludes=[],
win_no_prefer_redirects=False,
win_private_assemblies=False,
cipher=block_cipher)
pyz = PYZ(a.pure, a.zipped_data,
cipher=block_cipher)
exe = EXE(pyz,
a.scripts,
exclude_binaries=True,
name='gtk-demo',
debug=False,
strip=False,
upx=True,
console=False )
coll = COLLECT(exe,
a.binaries,
a.zipfiles,
a.datas,
strip=False,
upx=True,
name='gtk-demo')
app = BUNDLE(coll,
name='gtk-demo.app',
icon=None,
bundle_identifier=None)
主要是讲demos文件夹打包进应用程序
gtk-demo.py也要做一点改变
if getattr(sys, 'frozen', False):
# we are running in a bundle
DEMOROOTDIR = sys._MEIPASS
else:
# we are running in a normal Python environment
DEMOROOTDIR = os.path.abspath(os.path.dirname(__file__))
这个时候就需要使用这个配置文件来打包应用程序了,
注意:打包命令不一样了
pyinstaller --onedir -y gtk-demo.spec
打包完成后,打开gtk-demo.app
结果是这样的,如下图,很模糊
解决办法,spec文件中添加
‘NSHighResolutionCapable’: ‘True’,具体见下
图标后缀为icns,可以到这里下载
修改gtk-demo.spec文件
app中内容改成如下
app = BUNDLE(coll,
name='gtk-demo.app',
icon='gtk-demo.icns',
bundle_identifier=None,
info_plist={
'CFBundleName': APP_NAME,
'CFBundleDisplayName': APP_NAME,
'CFBundleGetInfoString': "Making gtk-demo",
'CFBundleIdentifier': "tk.xiaosanyu.gtk-demo",
'CFBundleVersion': "0.1.0",
'CFBundleShortVersionString': "0.1.0",
'NSHumanReadableCopyright': "Copyright © 2016, Xiaosanyu, All Rights Reserved",
'NSHighResolutionCapable': 'True',
})
再次运行上面的命令,打开新生成的app
熟悉的画面出现了,泪奔~~~
windows下的pygobject目前最高只支持Python3.4,所以先安装个Python3.4 32位的,下载地址https://www.python.org/ftp/python/3.4.4/python-3.4.4.msi
安装完毕后,
下载地址https://sourceforge.net/projects/pygobjectwin32/
下载得到文件pygi-aio-3.18.2_rev8-setup.exe
运行程序
如果你的项目需要其它的库,如Gstreamer,可自行安装
点击完成,完成安装。
pip3 install pyinstaller
同MAC上一样,需要对pyinstaller做一些修改
from PyInstaller.utils.hooks import get_gi_typelibs, collect_glib_share_files
binaries, datas, hiddenimports = get_gi_typelibs('GtkSource', '3.0')
datas += collect_glib_share_files('gtksourceview-3.0')
def pre_safe_import_module(api):
# PyGObject modules loaded through the gi repository are marked as
# MissingModules by modulegraph so we convert them to
# RuntimeModules so their hooks are loaded and run.
api.add_runtime_module(api.module_name)
from PyInstaller.utils.hooks import get_gi_typelibs, collect_glib_share_files
binaries, datas, hiddenimports = get_gi_typelibs('PangoFT2', '1.0')
添加hooks/pre_safe_import_module/hook-gi.repository.PangoFT2.py文件
C:\Python34_32bit\Lib\site-packages\PyInstaller\hooks\
pre_safe_import_module\hook-gi.repository.PangoFT2.py
内容同
hooks/pre_safe_import_module/hook-gi.repository.GtkSource.py
添加hooks/hook-gi.repository.fontconfig.py文件
C:\Python34_32bit\Lib\site-packages\PyInstaller\hooks\hook-gi.repository.fontconfig.py
from PyInstaller.utils.hooks import get_gi_typelibs, collect_glib_share_files
binaries, datas, hiddenimports = get_gi_typelibs('fontconfig', '2.0')
添加hooks/pre_safe_import_module/hook-gi.repository.fontconfig.py文件
C:\Python34_32bit\Lib\site-packages\PyInstaller\hooks\
pre_safe_import_module\hook-gi.repository.fontconfig.py
内容同
hooks/pre_safe_import_module/hook-gi.repository.GtkSource.py
添加hooks/hook-gi.repository.freetype2.py文件
C:\Python34_32bit\Lib\site-packages\PyInstaller\hooks\hook-gi.repository.freetype2.py
from PyInstaller.utils.hooks import get_gi_typelibs, collect_glib_share_files
binaries, datas, hiddenimports = get_gi_typelibs('freetype2', '2.0')
添加hooks/pre_safe_import_module/hook-gi.repository.freetype2.py文件
C:\Python34_32bit\Lib\site-packages\PyInstaller\hooks\
pre_safe_import_module\hook-gi.repository.freetype2.py
内容同
hooks/pre_safe_import_module/hook-gi.repository.GtkSource.py
修改hooks/hook-gi.repository.cairo.py
添加下面一句
hiddenimports += ["cairo"]
from PyInstaller.utils.hooks import get_gi_typelibs
binaries, datas, hiddenimports = get_gi_typelibs('GdkPixbuf', '2.0')
datas += collect_glib_translations('gdk-pixbuf')
将C:\Python34_32bit\Lib\site-packages\gnome\share\gir-1.0目录复制到C:\Python34_32bit\share\gir-1.0
把在MAC环境下的gtk-demo.spec文件拷贝过来,改一下
pathex=[‘c:\pygtk3’],
# -*- mode: python -*-
block_cipher = None
APP_NAME='gtk-demo'
added_files = [( 'demos', '' ),]
binaries_files=[]
a = Analysis(['gtk-demo.py'],
pathex=['c:\\pygtk3'],
binaries=binaries_files,
datas=added_files,
hiddenimports=["pygtkcompat"],
hookspath=[],
runtime_hooks=[],
excludes=[],
win_no_prefer_redirects=False,
win_private_assemblies=False,
cipher=block_cipher)
pyz = PYZ(a.pure, a.zipped_data,
cipher=block_cipher)
exe = EXE(pyz,
a.scripts,
exclude_binaries=True,
name='gtk-demo',
debug=False,
strip=False,
upx=True,
console=False )
coll = COLLECT(exe,
a.binaries,
a.zipfiles,
a.datas,
strip=False,
upx=True,
name='gtk-demo')
app = BUNDLE(coll,
name='gtk-demo.app',
icon='gtk-demo.icns',
bundle_identifier=None,
info_plist={
'CFBundleName': APP_NAME,
'CFBundleDisplayName': APP_NAME,
'CFBundleGetInfoString': "Making gtk-demo",
'CFBundleIdentifier': "tk.xiaosanyu.gtk-demo",
'CFBundleVersion': "0.1.0",
'CFBundleShortVersionString': "0.1.0",
'NSHumanReadableCopyright': "Copyright © 2016, Xiaosanyu, All Rights Reserved",
'NSHighResolutionCapable': 'True',
})
开始运行
cd c:\pygtk3
C:\Python34_32bit\Scripts\pyinstaller.exe -y --onedir gtk-demo.spec
解决办法,添加图标后就好了
图标后缀为ico,可以到这里下载
修改spec. EXE配置项中添加icon图标名
exe = EXE(pyz,
a.scripts,
exclude_binaries=True,
name='gtk-demo',
debug=False,
strip=False,
upx=True,
console=False,
icon="gtk-demo.ico" )
运行后打开dist/gtk-demo/gtk-demo.exe
Windows下的pygobject及打包到目前为止都比Mac上简单N倍,特别是pygobject的的安装,简直简单到没朋友。但是接下来的这个问题就为难到我了,那就是给exe文件添加版本信息。
参阅pyinstaller官方文档,发下有两个命令用来获取和添加版本信息,那就是
C:\Python34_32bit\Scripts\pyi-grab_version.exe
C:\Python34_32bit\Scripts\pyi-set_version.exe
然而这两个命令不支持Python3.x,博主(a87b01c14)经过一番努力,将这两个命令做了一些修改,现在分享给大家
主要是修改了
C:\Python34_32bit\Lib\site-packages\PyInstaller\utils\win32\versioninfo.py这个文件
# -*- coding: utf-8 -*-
#-----------------------------------------------------------------------------
# Copyright (c) 2013-2016, PyInstaller Development Team.
#
# Distributed under the terms of the GNU General Public License with exception
# for distributing bootloader.
#
# The full license is in the file COPYING.txt, distributed with this software.
#-----------------------------------------------------------------------------
import codecs
import struct
import pywintypes
import win32api
import pefile
import sys
# TODO implement read/write version information with pefile library.
# PE version info doc: http://msdn.microsoft.com/en-us/library/ms646981.aspx
def pefile_read_version(filename):
"""
Return structure like:
{
# Translation independent information.
# VS_FIXEDFILEINFO - Contains version information about a file. This information is language and code page independent.
u'FileVersion': (1, 2, 3, 4),
u'ProductVersion': (9, 10, 11, 12),
# PE files might contain several translations of version information.
# VS_VERSIONINFO - Depicts the organization of data in a file-version resource. It is the root structure that contains all other file-version information structures.
u'translations': {
'lang_id1' : {
u'Comments': u'日本語, Unicode 対応.',
u'CompanyName': u'your company.',
u'FileDescription': u'your file desc.',
u'FileVersion': u'1, 2, 3, 4',
u'InternalName': u'your internal name.',
u'LegalCopyright': u'your legal copyright.',
u'LegalTrademarks': u'your legal trademarks.',
u'OriginalFilename': u'your original filename.',
u'PrivateBuild': u'5, 6, 7, 8',
u'ProductName': u'your product name',
u'ProductVersion': u'9, 10, 11, 12',
u'SpecialBuild': u'13, 14, 15, 16',
},
'lang_id2' : {
...
}
}
}
Version info can contain multiple languages.
"""
# TODO
vers = {
'FileVersion': (0, 0, 0, 0),
'ProductVersion': (0, 0, 0, 0),
'translations': {
'lang_id1': {
'Comments': '',
'CompanyName': '',
'FileDescription': '',
'FileVersion': '',
'InternalName': '',
'LegalCopyright': '',
'LegalTrademarks': '',
'OriginalFilename': '',
'PrivateBuild': '',
'ProductName': '',
'ProductVersion': '',
'SpecialBuild': '',
}
}
}
pe = pefile.PE(filename)
#ffi = pe.VS_FIXEDFILEINFO
#vers['FileVersion'] = (ffi.FileVersionMS >> 16, ffi.FileVersionMS & 0xFFFF, ffi.FileVersionLS >> 16, ffi.FileVersionLS & 0xFFFF)
#vers['ProductVersion'] = (ffi.ProductVersionMS >> 16, ffi.ProductVersionMS & 0xFFFF, ffi.ProductVersionLS >> 16, ffi.ProductVersionLS & 0xFFFF)
#print(pe.VS_FIXEDFILEINFO.FileVersionMS)
# TODO Only first available language is used for now.
#vers = pe.FileInfo[0].StringTable[0].entries
from pprint import pprint
pprint(pe.VS_FIXEDFILEINFO)
print(dir(pe.VS_FIXEDFILEINFO))
print(repr(pe.VS_FIXEDFILEINFO))
print(pe.dump_info())
return vers
# Ensures no code from the executable is executed.
LOAD_LIBRARY_AS_DATAFILE = 2
STRINGTYPE = type(u'')
def getRaw(o):
return o.encode('UTF-16LE')
def decode(pathnm):
h = win32api.LoadLibraryEx(pathnm, 0, LOAD_LIBRARY_AS_DATAFILE)
if not nm :
print(pathnm+" don't have the version information")
win32api.FreeLibrary(h)
return
else:
nm=nm[0]
data = win32api.LoadResource(h, pefile.RESOURCE_TYPE['RT_VERSION'], nm)
vs = VSVersionInfo()
j = vs.fromRaw(data)
win32api.FreeLibrary(h)
return vs
class VSVersionInfo:
"""
WORD wLength; // length of the VS_VERSION_INFO structure
WORD wValueLength; // length of the Value member
WORD wType; // 1 means text, 0 means binary
WCHAR szKey[]; // Contains the Unicode string "VS_VERSION_INFO".
WORD Padding1[];
VS_FIXEDFILEINFO Value;
WORD Padding2[];
WORD Children[]; // zero or more StringFileInfo or VarFileInfo
// structures (or both) that are children of the
// current version structure.
"""
def __init__(self, ffi=None, kids=None):
self.ffi = ffi
self.kids = kids or []
def fromRaw(self, data):
i, (sublen, vallen, wType, nm) = parseCommon(data)
#vallen is length of the ffi, typ is 0, nm is 'VS_VERSION_INFO'.
i = int((i + 3) / 4) * 4
# Now a VS_FIXEDFILEINFO
self.ffi = FixedFileInfo()
j = self.ffi.fromRaw(data, i)
i = j
while i < sublen:
j = i
i, (csublen, cvallen, ctyp, nm) = parseCommon(data, i)
if nm.strip() == 'StringFileInfo':
sfi = StringFileInfo()
k = sfi.fromRaw(csublen, cvallen, nm, data, i, j+csublen)
self.kids.append(sfi)
i = k
else:
vfi = VarFileInfo()
k = vfi.fromRaw(csublen, cvallen, nm, data, i, j+csublen)
self.kids.append(vfi)
i = k
i = j + csublen
i = int((i + 3) / 4) * 4
return i
def toRaw(self):
nm = 'VS_VERSION_INFO'
rawffi = self.ffi.toRaw()
vallen = len(rawffi)
typ = 0
sublen = 6 + 2*len(nm) + 2
pad = b''
if sublen % 4:
pad = b'\000\000'
sublen = sublen + len(pad) + vallen
pad2 = b''
if sublen % 4:
pad2 = b'\000\000'
tmp=b''
for kid in self.kids:
tmp+=kid.toRaw()
sublen = sublen + len(pad2) + len(tmp)
result=(struct.pack('hhh', sublen, vallen, typ)
+ getRaw(nm) + b'\000\000' + pad + rawffi + pad2 + tmp)
return result
def __str__(self, indent=u''):
indent = indent + u' '
tmp = [kid.__str__(indent+u' ')
for kid in self.kids]
tmp = u', \n'.join(tmp)
return (u"""# UTF-8
#
# For more details about fixed file info 'ffi' see:
# http://msdn.microsoft.com/en-us/library/ms646997.aspx
VSVersionInfo(
%sffi=%s,
%skids=[
%s
%s]
)
""" % (indent, self.ffi.__str__(indent), indent, tmp, indent))
def parseCommon(data, start=0):
i = start + 6
(wLength, wValueLength, wType) = struct.unpack('3h', data[start:i])
i, text = parseUString(data, i, i+wLength)
return i, (wLength, wValueLength, wType, text)
def parseUString(data, start, limit):
i = start
while i < limit:
if data[i:i+2] == b'\000\000':
break
i += 2
if sys.version_info < (3, 0):
text = unicode(data[start:i], 'UTF-16LE')
else:
text = data[start:i].decode("UTF-16LE", "ignore")
i += 2
return i, text
class FixedFileInfo:
"""
DWORD dwSignature; //Contains the value 0xFEEFO4BD
DWORD dwStrucVersion; // binary version number of this structure.
// The high-order word of this member contains
// the major version number, and the low-order
// word contains the minor version number.
DWORD dwFileVersionMS; // most significant 32 bits of the file's binary
// version number
DWORD dwFileVersionLS; //
DWORD dwProductVersionMS; // most significant 32 bits of the binary version
// number of the product with which this file was
// distributed
DWORD dwProductVersionLS; //
DWORD dwFileFlagsMask; // bitmask that specifies the valid bits in
// dwFileFlags. A bit is valid only if it was
// defined when the file was created.
DWORD dwFileFlags; // VS_FF_DEBUG, VS_FF_PATCHED etc.
DWORD dwFileOS; // VOS_NT, VOS_WINDOWS32 etc.
DWORD dwFileType; // VFT_APP etc.
DWORD dwFileSubtype; // 0 unless VFT_DRV or VFT_FONT or VFT_VXD
DWORD dwFileDateMS;
DWORD dwFileDateLS;
"""
def __init__(self, filevers=(0, 0, 0, 0), prodvers=(0, 0, 0, 0),
mask=0x3f, flags=0x0, OS=0x40004, fileType=0x1,
subtype=0x0, date=(0, 0)):
self.sig = 0xfeef04bd
self.strucVersion = 0x10000
self.fileVersionMS = (filevers[0] << 16) | (filevers[1] & 0xffff)
self.fileVersionLS = (filevers[2] << 16) | (filevers[3] & 0xffff)
self.productVersionMS = (prodvers[0] << 16) | (prodvers[1] & 0xffff)
self.productVersionLS = (prodvers[2] << 16) | (prodvers[3] & 0xffff)
self.fileFlagsMask = mask
self.fileFlags = flags
self.fileOS = OS
self.fileType = fileType
self.fileSubtype = subtype
self.fileDateMS = date[0]
self.fileDateLS = date[1]
def fromRaw(self, data, i):
(self.sig,
self.strucVersion,
self.fileVersionMS,
self.fileVersionLS,
self.productVersionMS,
self.productVersionLS,
self.fileFlagsMask,
self.fileFlags,
self.fileOS,
self.fileType,
self.fileSubtype,
self.fileDateMS,
self.fileDateLS) = struct.unpack('13l', data[i:i+52])
return i+52
def toRaw(self):
return struct.pack('L12l', self.sig,
self.strucVersion,
self.fileVersionMS,
self.fileVersionLS,
self.productVersionMS,
self.productVersionLS,
self.fileFlagsMask,
self.fileFlags,
self.fileOS,
self.fileType,
self.fileSubtype,
self.fileDateMS,
self.fileDateLS)
def __str__(self, indent=u''):
fv = (self.fileVersionMS >> 16, self.fileVersionMS & 0xffff,
self.fileVersionLS >> 16, self.fileVersionLS & 0xFFFF)
pv = (self.productVersionMS >> 16, self.productVersionMS & 0xffff,
self.productVersionLS >> 16, self.productVersionLS & 0xFFFF)
fd = (self.fileDateMS, self.fileDateLS)
tmp = [u'FixedFileInfo(',
u'# filevers and prodvers should be always a tuple with four items: (1, 2, 3, 4)',
u'# Set not needed items to zero 0.',
u'filevers=%s,' % str(fv),
u'prodvers=%s,' % str(pv),
u"# Contains a bitmask that specifies the valid bits 'flags'r",
u'mask=%s,' % hex(self.fileFlagsMask),
u'# Contains a bitmask that specifies the Boolean attributes of the file.',
u'flags=%s,' % hex(self.fileFlags),
u'# The operating system for which this file was designed.',
u'# 0x4 - NT and there is no need to change it.',
u'OS=%s,' % hex(self.fileOS),
u'# The general type of file.',
u'# 0x1 - the file is an application.',
u'fileType=%s,' % hex(self.fileType),
u'# The function of the file.',
u'# 0x0 - the function is not defined for this fileType',
u'subtype=%s,' % hex(self.fileSubtype),
u'# Creation date and time stamp.',
u'date=%s' % str(fd),
u')'
]
return (u'\n'+indent+u' ').join(tmp)
class StringFileInfo(object):
"""
WORD wLength; // length of the version resource
WORD wValueLength; // length of the Value member in the current
// VS_VERSION_INFO structure
WORD wType; // 1 means text, 0 means binary
WCHAR szKey[]; // Contains the Unicode string 'StringFileInfo'.
WORD Padding[];
StringTable Children[]; // list of zero or more String structures
"""
def __init__(self, kids=None):
self.name = u'StringFileInfo'
self.kids = kids or []
def fromRaw(self, sublen, vallen, name, data, i, limit):
self.name = name
while i < limit:
st = StringTable()
j = st.fromRaw(data, i, limit)
self.kids.append(st)
i = j
return i
def toRaw(self):
vallen = 0
typ = 1
sublen = 6 + 2*len(self.name) + 2
pad = b''
if sublen % 4:
pad = b'\000\000'
tmp=b''
for kid in self.kids:
tmp+=kid.toRaw()
sublen = sublen + len(pad) + len(tmp)
if tmp[-2:] == '\000\000':
sublen = sublen - 2
result=(struct.pack('hhh', sublen, vallen, typ)
+ getRaw(self.name) + b'\000\000' + pad + tmp)
return result
def __str__(self, indent=u''):
newindent = indent + u' '
tmp = [kid.__str__(newindent)
for kid in self.kids]
tmp = u', \n'.join(tmp)
return (u'%sStringFileInfo(\n%s[\n%s\n%s])'
% (indent, newindent, tmp, newindent))
class StringTable:
"""
WORD wLength;
WORD wValueLength;
WORD wType;
WCHAR szKey[];
String Children[]; // list of zero or more String structures.
"""
def __init__(self, name=None, kids=None):
self.name = name or u''
self.kids = kids or []
def fromRaw(self, data, i, limit):
i, (cpsublen, cpwValueLength, cpwType, self.name) = parseCodePage(data, i, limit) # should be code page junk
#i = ((i + 3) / 4) * 4
while i < limit:
ss = StringStruct()
j = ss.fromRaw(data, i, limit)
i = j
self.kids.append(ss)
i = int((i + 3) / 4) * 4
return i
def toRaw(self):
vallen = 0
typ = 1
sublen = 6 + 2*len(self.name) + 2
tmp = b''
for kid in self.kids:
raw = kid.toRaw()
if len(raw) % 4:
raw = raw + b'\000\000'
tmp+=raw
sublen += len(tmp)
if tmp[-2:] == '\000\000':
sublen -= 2
result= (struct.pack('hhh', sublen, vallen, typ)
+ getRaw(self.name) + b'\000\000' + tmp)
return result
def __str__(self, indent=u''):
newindent = indent + u' '
tmp = map(str, self.kids)
tmp = (u',\n%s' % newindent).join(tmp)
return (u"%sStringTable(\n%su'%s',\n%s[%s])"
% (indent, newindent, self.name, newindent, tmp))
class StringStruct:
"""
WORD wLength;
WORD wValueLength;
WORD wType;
WCHAR szKey[];
WORD Padding[];
String Value[];
"""
def __init__(self, name=None, val=None):
self.name = name or u''
self.val = val or u''
def fromRaw(self, data, i, limit):
i, (sublen, vallen, typ, self.name) = parseCommon(data, i)
limit = i + sublen
i = int((i + 3) / 4) * 4
i, self.val = parseUString(data, i, limit)
return i
def toRaw(self):
if type(self.name) is STRINGTYPE:
# Convert unicode object to byte string.
raw_name = self.name.encode('UTF-16LE')
if type(self.val) is STRINGTYPE:
# Convert unicode object to byte string.
raw_val = self.val.encode('UTF-16LE')
# TODO document the size of vallen and sublen.
vallen = len(raw_val) + 2
typ = 1
sublen = 6 + len(raw_name) + 2
pad = b''
if sublen % 4:
pad = b'\000\000'
sublen = sublen + len(pad) + vallen
abcd = (struct.pack('hhh', sublen, vallen, typ)
+ raw_name + b'\000\000' + pad
+ raw_val + b'\000\000')
return abcd
def __str__(self, indent=''):
return u"StringStruct(u'%s', u'%s')" % (self.name, self.val)
def parseCodePage(data, i, limit):
i, (sublen, wValueLength, wType, nm) = parseCommon(data, i)
return i, (sublen, wValueLength, wType, nm)
class VarFileInfo:
"""
WORD wLength; // length of the version resource
WORD wValueLength; // length of the Value member in the current
// VS_VERSION_INFO structure
WORD wType; // 1 means text, 0 means binary
WCHAR szKey[]; // Contains the Unicode string 'VarFileInfo'.
WORD Padding[];
Var Children[]; // list of zero or more Var structures
"""
def __init__(self, kids=None):
self.kids = kids or []
def fromRaw(self, sublen, vallen, name, data, i, limit):
self.sublen = sublen
self.vallen = vallen
self.name = name
i = int((i + 3) / 4) * 4
while i < limit:
vs = VarStruct()
j = vs.fromRaw(data, i, limit)
self.kids.append(vs)
i = j
return i
def toRaw(self):
self.vallen = 0
self.wType = 1
self.name = 'VarFileInfo'
sublen = 6 + 2*len(self.name) + 2
pad = b''
if sublen % 4:
pad = b'\000\000'
tmp=b''
for kid in self.kids:
tmp+=kid.toRaw()
self.sublen = sublen + len(pad) + len(tmp)
result= (struct.pack('hhh', self.sublen, self.vallen, self.wType)
+ getRaw(self.name) + b'\000\000' + pad + tmp)
return result
def __str__(self, indent=''):
tmp = map(str, self.kids)
return "%sVarFileInfo([%s])" % (indent, ', '.join(tmp))
class VarStruct:
"""
WORD wLength; // length of the version resource
WORD wValueLength; // length of the Value member in the current
// VS_VERSION_INFO structure
WORD wType; // 1 means text, 0 means binary
WCHAR szKey[]; // Contains the Unicode string 'Translation'
// or a user-defined key string value
WORD Padding[]; //
WORD Value[]; // list of one or more values that are language
// and code-page identifiers
"""
def __init__(self, name=None, kids=None):
self.name = name or u''
self.kids = kids or []
def fromRaw(self, data, i, limit):
i, (self.sublen, self.wValueLength, self.wType, self.name) = parseCommon(data, i)
i = int((i + 3) / 4) * 4
for j in range(int(self.wValueLength/2)):
kid = struct.unpack('h', data[i:i+2])[0]
self.kids.append(kid)
i += 2
return i
def toRaw(self):
self.wValueLength = len(self.kids) * 2
self.wType = 0
sublen = 6 + 2*len(self.name) + 2
pad = b''
if sublen % 4:
pad =b'\000\000'
self.sublen = sublen + len(pad) + self.wValueLength
tmp=b''
for kid in self.kids:
tmp+=struct.pack('h', kid)
return (struct.pack('hhh', self.sublen, self.wValueLength, self.wType)
+ getRaw(self.name) + b'\000\000' + pad + tmp)
def __str__(self, indent=u''):
return u"VarStruct(u'%s', %r)" % (self.name, self.kids)
def SetVersion(exenm, versionfile):
if isinstance(versionfile, VSVersionInfo):
vs = versionfile
else:
fp = codecs.open(versionfile, 'rU', 'utf-8')
txt=fp.read()
fp.close()
vs = eval(txt)
data=vs.toRaw()
hdst = win32api.BeginUpdateResource(exenm, 0)
win32api.UpdateResource(hdst, pefile.RESOURCE_TYPE['RT_VERSION'], 1, data)
win32api.EndUpdateResource (hdst, 0)
还要修改一个文件
C:\Python34_32bit\Lib\site-packages\PyInstaller\utils\cliutil
s\grab_version.py
try:
vs = PyInstaller.utils.win32.versioninfo.decode(args.exe_file)
if vs is None:
return
fp = codecs.open(args.out_filename, 'w', 'utf-8')
fp.write(str(vs))
fp.close()
print(('Version info written to: %s' % args.out_filename))
except KeyboardInterrupt:
raise SystemExit("Aborted by user request.")
pyi-grab_version 后面参数为一个带有版本信息的exe程序,这里我选用java.exe来做测试,
C:\Python34_32bit\Scripts\pyi-grab_version.exe "C:\Program Files\Java\jre6\bin\java.exe" version.txt
version.txt用来存储获取到的信息,不添加此参数,则默认写到file_version_info.txt文件中
version.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=(6, 0, 200, 2),
prodvers=(6, 0, 200, 2),
# 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=0x4,
# 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'000004b0',
[StringStruct(u'CompanyName', u'Sun Microsystems, Inc.'),
StringStruct(u'FileDescription', u'Java(TM) Platform SE binary'),
StringStruct(u'FileVersion', u'6.0.200.2'),
StringStruct(u'Full Version', u'1.6.0_20-b02'),
StringStruct(u'InternalName', u'java'),
StringStruct(u'LegalCopyright', u'Copyright © 2004'),
StringStruct(u'OriginalFilename', u'java.exe'),
StringStruct(u'ProductName', u'Java(TM) Platform SE 6 U20'),
StringStruct(u'ProductVersion', u'6.0.200.2')])
]),
VarFileInfo([VarStruct(u'Translation', [0, 1200])])
]
)
然后我们可以修改一下里面的内容
我修改后的内容保存在file_version_info.txt文件中,文件要是utf8格式的,内容如下
# 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=(1, 0, 0, 0),
prodvers=(1, 0, 0, 0),
# 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(
'040904E4',
[StringStruct('CompanyName', 'xiaosanyu'),
StringStruct('FileDescription', 'Gtk demo'),
StringStruct('FileVersion', '1.0.0.0'),
StringStruct('LegalCopyright', 'Copyright © 2016 GNOME Software, Inc.'),
StringStruct('LegalTrademarks', 'gtk-demo ® is a registered trademark of GNOME Software, Inc.'),
StringStruct('OriginalFilename', 'gtk-demo.exe'),
StringStruct('ProductName', 'Gtk-demo'),
StringStruct('ProductVersion', '1.0'),
StringStruct('Comments', 'gtk-demo 1'),
StringStruct('Subversion Revision', '0'),
StringStruct('CompileDate', 'Monday, August 15, 2016 9:44 AM')])
]),
VarFileInfo([VarStruct('Translation', [1033, 1252])])
]
)
现在使用pyi-set_version.exe命令将file_version_info.txt中版本信息写入到get-demo.exe中
C:\Python34_32bit\Scripts\pyi-set_version.exe file_version_info.txt dist\gtk-demo\gtk-demo.exe
查看结果
看来版本信息是写入了,可是悲剧的事情发生了,打开应用程序,报错
*
Fata Error! can’t open self C:\pygtk3\dist\gtk-demo\gtk-demo.exe or archive C:\pygtk3\dist\gtk-demo\gtk-demo.pkg*
只能另寻它法了
尝试过 打包的时候添加
--version-file file_version_info.txt
没有效果
最后在
C:\Users\xiaosanyu>C:\Python34_32bit\Lib\site-packages\PyInstaller\building\api.py文件中找到了答案
在spec配置文件的EXE项中添加version参数
exe = EXE(pyz,
a.scripts,
exclude_binaries=True,
name='gtk-demo',
debug=False,
strip=False,
upx=True,
console=False,
icon="gtk-demo.ico",
version="file_version_info.txt" )
再次运行
C:\Python34_32bit\Scripts\pyinstaller.exe -y --onedir gtk-demo.spec
打包成功,并且也有版本信息。
CSS文件中,“button”改成”.button”号;“window”前面加“.”号没有效果,只能将“window”改成“#window”,
然后在py文件中 添加window.set_name("window")
最后使用glib-compile-resources.exe重新编译资源文件
要下载Python3.4 64位,然后安装pyinstaller,然后安装pygobject,其它都同32位的打包
看pyinstaller介绍是要使用Cython,暂时还没有研究,后面空了研究透了,再放上来
代码下载地址:http://download.csdn.net/detail/a87b01c14/9594728