[Win32] Refactor bbreeze script

* In setup.py put web and deluged back into console_script as the gtkui hack in
bbfreeze is a windows only issue for popup cmd windows showing.
 * Altered the bbreeze script to find any deluge scripts in pythonpath.
 * Use setIcon now in bbfreeze and use icon from package.
 * Use version stamp from pywin32.
This commit is contained in:
Calum Lind 2015-08-24 12:39:25 +01:00
parent 0ee8c7d70f
commit 1c3e14919f
5 changed files with 81 additions and 553 deletions

View File

@ -1,292 +0,0 @@
# -*- coding: latin-1 -*-
##
## Copyright (c) 2000-2013 Thomas Heller
##
## Permission is hereby granted, free of charge, to any person obtaining
## a copy of this software and associated documentation files (the
## "Software"), to deal in the Software without restriction, including
## without limitation the rights to use, copy, modify, merge, publish,
## distribute, sublicense, and/or sell copies of the Software, and to
## permit persons to whom the Software is furnished to do so, subject to
## the following conditions:
##
## The above copyright notice and this permission notice shall be
## included in all copies or substantial portions of the Software.
##
## THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
## EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
## MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
## NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
## LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
## OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
## WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
##
#
# $Id: VersionInfo.py 738 2013-09-07 10:11:45Z theller $
#
# $Log$
# Revision 1.3 2004/01/16 10:45:31 theller
# Move py2exe from the sandbox directory up to the root dir.
#
# Revision 1.3 2003/12/29 13:44:57 theller
# Adapt for Python 2.3.
#
# Revision 1.2 2003/09/18 20:19:57 theller
# Remove a 2.3 warning, but mostly this checkin is to test the brand new
# py2exe-checkins mailing list.
#
# Revision 1.1 2003/08/29 12:30:52 mhammond
# New py2exe now uses the old resource functions :)
#
# Revision 1.1 2002/01/29 09:30:55 theller
# version 0.3.0
#
# Revision 1.2 2002/01/14 19:08:05 theller
# Better (?) Unicode handling.
#
# Revision 1.1 2002/01/07 10:30:32 theller
# Create a version resource.
#
#
import struct
VOS_NT_WINDOWS32 = 0x00040004
VFT_APP = 0x00000001
RT_VERSION = 16
class VersionError(Exception):
pass
def w32_uc(text):
"""convert a string into unicode, then encode it into UTF-16
little endian, ready to use for win32 apis"""
if type(text) is str:
return unicode(text, "unicode-escape").encode("utf-16-le")
return unicode(text).encode("utf-16-le")
class VS_FIXEDFILEINFO:
dwSignature = 0xFEEF04BDL
dwStrucVersion = 0x00010000
dwFileVersionMS = 0x00010000
dwFileVersionLS = 0x00000001
dwProductVersionMS = 0x00010000
dwProductVersionLS = 0x00000001
dwFileFlagsMask = 0x3F
dwFileFlags = 0
dwFileOS = VOS_NT_WINDOWS32
dwFileType = VFT_APP
dwFileSubtype = 0
dwFileDateMS = 0
dwFileDateLS = 0
fmt = "13L"
def __init__(self, version):
import string
version = string.replace(version, ",", ".")
fields = string.split(version + '.0.0.0.0', ".")[:4]
fields = map(string.strip, fields)
try:
self.dwFileVersionMS = int(fields[0]) * 65536 + int(fields[1])
self.dwFileVersionLS = int(fields[2]) * 65536 + int(fields[3])
except ValueError:
raise VersionError("could not parse version number '%s'" % version)
def __str__(self):
return struct.pack(self.fmt,
self.dwSignature,
self.dwStrucVersion,
self.dwFileVersionMS,
self.dwFileVersionLS,
self.dwProductVersionMS,
self.dwProductVersionLS,
self.dwFileFlagsMask,
self.dwFileFlags,
self.dwFileOS,
self.dwFileType,
self.dwFileSubtype,
self.dwFileDateMS,
self.dwFileDateLS)
def align(data):
pad = - len(data) % 4
return data + '\000' * pad
class VS_STRUCT:
items = ()
def __str__(self):
szKey = w32_uc(self.name)
ulen = len(szKey)+2
value = self.get_value()
data = struct.pack("h%ss0i" % ulen, self.wType, szKey) + value
data = align(data)
for item in self.items:
data = data + str(item)
wLength = len(data) + 4 # 4 bytes for wLength and wValueLength
wValueLength = len(value)
return self.pack("hh", wLength, wValueLength, data)
def pack(self, fmt, len, vlen, data):
return struct.pack(fmt, len, vlen) + data
def get_value(self):
return ""
class String(VS_STRUCT):
wType = 1
items = ()
def __init__(self, name_value):
(name, value) = name_value
self.name = name
if value:
self.value = value + '\000' # strings must be zero terminated
else:
self.value = value
def pack(self, fmt, len, vlen, data):
# ValueLength is measured in WORDS, not in BYTES!
return struct.pack(fmt, len, vlen/2) + data
def get_value(self):
return w32_uc(self.value)
class StringTable(VS_STRUCT):
wType = 1
def __init__(self, name, strings):
self.name = name
self.items = map(String, strings)
class StringFileInfo(VS_STRUCT):
wType = 1
name = "StringFileInfo"
def __init__(self, name, strings):
self.items = [StringTable(name, strings)]
class Var(VS_STRUCT):
# MSDN says:
# If you use the Var structure to list the languages your
# application or DLL supports instead of using multiple version
# resources, use the Value member to contain an array of DWORD
# values indicating the language and code page combinations
# supported by this file. The low-order word of each DWORD must
# contain a Microsoft language identifier, and the high-order word
# must contain the IBM® code page number. Either high-order or
# low-order word can be zero, indicating that the file is language
# or code page independent. If the Var structure is omitted, the
# file will be interpreted as both language and code page
# independent.
wType = 0
name = "Translation"
def __init__(self, value):
self.value = value
def get_value(self):
return struct.pack("l", self.value)
class VarFileInfo(VS_STRUCT):
wType = 1
name = "VarFileInfo"
def __init__(self, *names):
self.items = map(Var, names)
def get_value(self):
return ""
class VS_VERSIONINFO(VS_STRUCT):
wType = 0 # 0: binary data, 1: text data
name = "VS_VERSION_INFO"
def __init__(self, version, items):
self.value = VS_FIXEDFILEINFO(version)
self.items = items
def get_value(self):
return str(self.value)
class Version(object):
def __init__(self,
version,
comments = None,
company_name = None,
file_description = None,
internal_name = None,
legal_copyright = None,
legal_trademarks = None,
original_filename = None,
private_build = None,
product_name = None,
product_version = None,
special_build = None):
self.version = version
strings = []
if comments is not None:
strings.append(("Comments", comments))
if company_name is not None:
strings.append(("CompanyName", company_name))
if file_description is not None:
strings.append(("FileDescription", file_description))
strings.append(("FileVersion", version))
if internal_name is not None:
strings.append(("InternalName", internal_name))
if legal_copyright is not None:
strings.append(("LegalCopyright", legal_copyright))
if legal_trademarks is not None:
strings.append(("LegalTrademarks", legal_trademarks))
if original_filename is not None:
strings.append(("OriginalFilename", original_filename))
if private_build is not None:
strings.append(("PrivateBuild", private_build))
if product_name is not None:
strings.append(("ProductName", product_name))
strings.append(("ProductVersion", product_version or version))
if special_build is not None:
strings.append(("SpecialBuild", special_build))
self.strings = strings
def resource_bytes(self):
vs = VS_VERSIONINFO(self.version,
[StringFileInfo("040904B0",
self.strings),
VarFileInfo(0x04B00409)])
return str(vs)
def test():
import sys
sys.path.append("c:/tmp")
from hexdump import hexdump
version = Version("1, 0, 0, 1",
comments = "ümläut comments",
company_name = "No Company",
file_description = "silly application",
internal_name = "silly",
legal_copyright = u"Copyright © 2003",
## legal_trademark = "",
original_filename = "silly.exe",
private_build = "test build",
product_name = "silly product",
product_version = None,
## special_build = ""
)
hexdump(version.resource_bytes())
if __name__ == '__main__':
import sys
sys.path.append("d:/nbalt/tmp")
from hexdump import hexdump
test()

View File

@ -1,35 +1,56 @@
import glob
import os
import re
import shutil
import sys
import bbfreeze.recipes
import bbfreeze
import gtk
import icon
import win32api
from bbfreeze import Freezer
from VersionInfo import Version
from win32verstamp import stamp
import deluge.common
# Get build_version from installed deluge
class VersionInfo(object):
def __init__(self, version, internalName = None, originalFileName = None,
comments = None, company = None, description = None,
copyright = None, trademarks = None, product = None, dll = False,
debug = False, verbose = True):
parts = version.split(".")
while len(parts) < 4:
parts.append("0")
self.version = ".".join(parts)
self.internal_name = internalName
self.original_filename = originalFileName
self.comments = comments
self.company = company
self.description = description
self.copyright = copyright
self.trademarks = trademarks
self.product = product
self.dll = dll
self.debug = debug
self.verbose = verbose
# Get build_version from installed deluge.
build_version = deluge.common.get_version()
print "Deluge Version: %s" % build_version
python_path = os.path.dirname(sys.executable)
if python_path.endswith("Scripts"):
python_path = python_path[:-8]
python_path += os.path.sep
print "Python Path: %s" % python_path
gtk_root = os.path.join(gtk.__path__[0], "..\\runtime\\")
# Include python modules not picked up automatically by bbfreeze
gtk_root = os.path.join(gtk.__path__[0], "..", "runtime") + os.path.sep
# Include python modules not picked up automatically by bbfreeze.
includes = ("libtorrent", "cairo", "pangocairo", "atk", "pango", "twisted.internet.utils",
"gio", "gzip", "email.mime.multipart", "email.mime.text", "_cffi_backend")
excludes = ("numpy", "OpenGL", "psyco", "win32ui")
dst = "..\\build-win32\\deluge-bbfreeze-" + build_version + "\\"
build_dir = "..\\build-win32\\deluge-bbfreeze-" + build_version + "\\"
# Need to override bbfreeze function so that it includes all gtk libraries
# in the installer so users don't require a separate GTK+ installation.
@ -37,55 +58,58 @@ def recipe_gtk_override(mf):
return True
bbfreeze.recipes.recipe_gtk_and_friends = recipe_gtk_override
f = Freezer(dst, includes=includes, excludes=excludes)
f.include_py = False
# Can/should we grab this from setup.py entry_points somehow
gui_scripts = ["deluge", "deluged", "deluge-web", "deluge-gtk"]
console_scripts = ["deluge-debug", "deluged-debug", "deluge-web-debug", "deluge-console"]
fzr = bbfreeze.Freezer(build_dir, includes=includes, excludes=excludes)
fzr.include_py = False
fzr.setIcon(os.path.join(os.path.dirname(deluge.common.__file__), "ui", "data", "pixmaps", "deluge.ico"))
# Copy the scripts to get rid of the '-script' suffix before adding to freezer
for script in gui_scripts:
shutil.copy(python_path + "Scripts/%s-script.pyw" % script, python_path + "Scripts/%s.pyw" % script)
f.addScript(python_path + "Scripts/%s.pyw" % script, gui_only=True)
for script in console_scripts:
shutil.copy(python_path + "Scripts/%s-script.py" % script, python_path + "Scripts/%s.py" % script)
f.addScript(python_path + "Scripts/%s.py" % script, gui_only=False)
f() # starts the freezing process
# TODO: Can/should we grab the script list from setup.py entry_points somehow.
# Clean up the duplicated scripts
for script in gui_scripts:
os.remove(python_path + "Scripts/%s.pyw" % script)
for script in console_scripts:
os.remove(python_path + "Scripts/%s.py" % script)
# Hide cmd console popup for these console entries force gui_script True.
force_gui = ["deluge-web", "deluged", "deluge-console"]
script_list = []
for script in glob.glob(python_path + "Scripts\\deluge*-script.py*"):
# Copy the scripts to remove the '-script' suffix before adding to freezer.
new_script = script.replace("-script", "")
shutil.copy(script, new_script)
# add icons to the exe files
icon_path = os.path.join(os.path.dirname(__file__), "deluge.ico")
for script in console_scripts + gui_scripts:
icon.CopyIcons(dst + script + ".exe", icon_path)
script_splitext = os.path.splitext(os.path.basename(new_script))
if script_splitext[1] == "pyw" or script_splitext[0] in force_gui:
gui_script = True
else:
gui_script = False
try:
fzr.addScript(new_script, gui_only=gui_script)
script_list.append(new_script)
except:
os.remove(script)
# Start the freezing process.
fzr()
# Clean up the duplicated scripts.
for script in script_list:
os.remove(script)
# Add version information to exe files.
for script in console_scripts + gui_scripts:
script_exe = script + ".exe"
version = Version(build_version,
file_description="Deluge Bittorrent Client",
company_name="Deluge Team",
legal_copyright="GPLv3",
original_filename=script_exe,
product_name="Deluge",
product_version=build_version)
for script in script_list:
script_exe = os.path.splitext(os.path.basename(script))[0] + ".exe"
if not re.search('[a-zA-Z_-]', build_version):
versionInfo = VersionInfo(build_version,
description="Deluge Bittorrent Client",
company="Deluge Team",
product="Deluge",
copyright="GPLv3")
stamp(os.path.join(build_dir, script_exe), versionInfo)
pyhandle = win32api.BeginUpdateResource(os.path.join(dst, script_exe), 0)
win32api.UpdateResource(pyhandle, 16, 1, version.resource_bytes())
win32api.EndUpdateResource(pyhandle, 0)
# exclude files which are already included in GTK or Windows
# Exclude files which are already included in GTK or Windows.
excludeDlls = ("MSIMG32.dll", "MSVCR90.dll", "MSVCP90.dll", "POWRPROF.dll", "DNSAPI.dll", "USP10.dll")
for file in excludeDlls:
for filename in glob.glob(dst + file):
for dll in excludeDlls:
for filename in glob.glob(os.path.join(build_dir, dll)):
print "removing file:", filename
os.remove(filename)
# copy gtk locale files
# Copy gtk locale files.
gtk_locale = os.path.join(gtk_root, 'share/locale')
locale_include_list = ['gtk20.mo', 'locale.alias']
@ -96,9 +120,9 @@ def ignored_files(adir, filenames):
if not os.path.isdir(os.path.join(adir, filename))
and filename not in locale_include_list
]
shutil.copytree(gtk_locale, os.path.join(dst, 'share/locale'), ignore=ignored_files)
shutil.copytree(gtk_locale, os.path.join(build_dir, 'share/locale'), ignore=ignored_files)
# copy gtk theme files
# Copy gtk theme files.
theme_include_list = [
[gtk_root, "share/icons/hicolor/index.theme"],
[gtk_root, "lib/gtk-2.0/2.10.0/engines"],
@ -110,15 +134,16 @@ theme_include_list = [
for path_root, path in theme_include_list:
full_path = os.path.join(path_root, path)
if os.path.isdir(full_path):
shutil.copytree(full_path, os.path.join(dst, path))
shutil.copytree(full_path, os.path.join(build_dir, path))
else:
dst_dir = os.path.join(dst, os.path.dirname(path))
dst_dir = os.path.join(build_dir, os.path.dirname(path))
try:
os.makedirs(dst_dir)
except:
pass
shutil.copy(full_path, dst_dir)
# Copy version info to file for nsis script.
file = open('VERSION.tmp', 'w')
file.write("build_version = \"%s\"" % build_version)
file.close()

Binary file not shown.

Before

Width:  |  Height:  |  Size: 25 KiB

View File

@ -1,205 +0,0 @@
#! /usr/bin/env python
# Copyright (C) 2005, Giovanni Bajo
# Based on previous work under copyright (c) 2002 McMillan Enterprises, Inc.
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
# This code is courtesy of Thomas Heller, who
# has kindly donated it to this project.
RT_ICON = 3
RT_GROUP_ICON = 14
LOAD_LIBRARY_AS_DATAFILE = 2
import struct
import types
try:
StringTypes = types.StringTypes
except AttributeError:
StringTypes = [type("")]
class Structure:
def __init__(self):
size = self._sizeInBytes = struct.calcsize(self._format_)
self._fields_ = list(struct.unpack(self._format_, '\000' * size))
indexes = self._indexes_ = {}
for i in range(len(self._names_)):
indexes[self._names_[i]] = i
def dump(self):
# print "I: DUMP of", self
for name in self._names_:
if name[0] != '_':
# print "I: %20s = %s" % (name, getattr(self, name))
pass
def __getattr__(self, name):
if name in self._names_:
index = self._indexes_[name]
return self._fields_[index]
try:
return self.__dict__[name]
except KeyError:
raise AttributeError(name)
def __setattr__(self, name, value):
if name in self._names_:
index = self._indexes_[name]
self._fields_[index] = value
else:
self.__dict__[name] = value
def tostring(self):
return apply(struct.pack, [self._format_, ] + self._fields_)
def fromfile(self, file):
data = file.read(self._sizeInBytes)
self._fields_ = list(struct.unpack(self._format_, data))
class ICONDIRHEADER(Structure):
_names_ = "idReserved", "idType", "idCount"
_format_ = "hhh"
class ICONDIRENTRY(Structure):
_names_ = "bWidth", "bHeight", "bColorCount", "bReserved", "wPlanes", "wBitCount", "dwBytesInRes", "dwImageOffset"
_format_ = "bbbbhhii"
class GRPICONDIR(Structure):
_names_ = "idReserved", "idType", "idCount"
_format_ = "hhh"
class GRPICONDIRENTRY(Structure):
_names_ = "bWidth", "bHeight", "bColorCount", "bReserved", "wPlanes", "wBitCount", "dwBytesInRes", "nID"
_format_ = "bbbbhhih"
class IconFile:
def __init__(self, path):
self.path = path
file = open(path, "rb")
self.entries = []
self.images = []
header = self.header = ICONDIRHEADER()
header.fromfile(file)
for i in range(header.idCount):
entry = ICONDIRENTRY()
entry.fromfile(file)
self.entries.append(entry)
for e in self.entries:
file.seek(e.dwImageOffset, 0)
self.images.append(file.read(e.dwBytesInRes))
def grp_icon_dir(self):
return self.header.tostring()
def grp_icondir_entries(self, id=1):
data = ""
for entry in self.entries:
e = GRPICONDIRENTRY()
for n in e._names_[:-1]:
setattr(e, n, getattr(entry, n))
e.nID = id
id = id + 1
data = data + e.tostring()
return data
def CopyIcons_FromIco(dstpath, srcpath, id=1): # NOQA
import win32api
icons = map(IconFile, srcpath)
print "I: Updating icons from", srcpath, "to", dstpath
hdst = win32api.BeginUpdateResource(dstpath, 0)
iconid = 1
for i in range(len(icons)):
f = icons[i]
data = f.grp_icon_dir()
data = data + f.grp_icondir_entries(iconid)
win32api.UpdateResource(hdst, RT_GROUP_ICON, i, data)
# print "I: Writing RT_GROUP_ICON %d resource with %d bytes" % (i, len(data))
for data in f.images:
win32api.UpdateResource(hdst, RT_ICON, iconid, data)
# print "I: Writing RT_ICON %d resource with %d bytes" % (iconid, len(data))
iconid = iconid + 1
win32api.EndUpdateResource(hdst, 0)
def CopyIcons(dstpath, srcpath): # NOQA
import os.path
import string
if type(srcpath) in StringTypes:
srcpath = [srcpath]
def splitter(s):
try:
srcpath, index = map(string.strip, string.split(s, ','))
return srcpath, int(index)
except ValueError:
return s, None
srcpath = map(splitter, srcpath)
# print "I: SRCPATH", srcpath
if len(srcpath) > 1:
# At the moment, we support multiple icons only from .ico files
srcs = []
for s in srcpath:
e = os.path.splitext(s[0])[1]
if string.lower(e) != '.ico':
raise ValueError("multiple icons supported only from .ico files")
if s[1] is not None:
raise ValueError("index not allowed for .ico files")
srcs.append(s[0])
return CopyIcons_FromIco(dstpath, srcs)
srcpath, index = srcpath[0]
srcext = os.path.splitext(srcpath)[1]
if string.lower(srcext) == '.ico':
return CopyIcons_FromIco(dstpath, [srcpath])
if index is not None:
print "I: Updating icons from", srcpath, ", %d to" % index, dstpath
else:
print "I: Updating icons from", srcpath, "to", dstpath
import win32api
hdst = win32api.BeginUpdateResource(dstpath, 0)
hsrc = win32api.LoadLibraryEx(srcpath, 0, LOAD_LIBRARY_AS_DATAFILE)
if index is None:
grpname = win32api.EnumResourceNames(hsrc, RT_GROUP_ICON)[0]
elif index >= 0:
grpname = win32api.EnumResourceNames(hsrc, RT_GROUP_ICON)[index]
else:
grpname = -index
data = win32api.LoadResource(hsrc, RT_GROUP_ICON, grpname)
win32api.UpdateResource(hdst, RT_GROUP_ICON, grpname, data)
for iconname in win32api.EnumResourceNames(hsrc, RT_ICON):
data = win32api.LoadResource(hsrc, RT_ICON, iconname)
win32api.UpdateResource(hdst, RT_ICON, iconname, data)
win32api.FreeLibrary(hsrc)
win32api.EndUpdateResource(hdst, 0)
if __name__ == "__main__":
import sys
dstpath = sys.argv[1]
srcpath = sys.argv[2:]
CopyIcons(dstpath, srcpath)

View File

@ -283,13 +283,13 @@ if not windows_check() and os.path.exists(desktop_data):
entry_points = {
"console_scripts": [
"deluge-console = deluge.ui.console:start"
"deluge-console = deluge.ui.console:start",
"deluge-web = deluge.ui.web:start",
"deluged = deluge.main:start_daemon"
],
"gui_scripts": [
"deluge = deluge.main:start_ui",
"deluge-gtk = deluge.ui.gtkui:start",
"deluge-web = deluge.ui.web:start",
"deluged = deluge.main:start_daemon"
"deluge-gtk = deluge.ui.gtkui:start"
]
}