1#!/usr/local/bin/python3.8 2# vim:fileencoding=utf-8 3# License: GPLv3 Copyright: 2016, Kovid Goyal <kovid at kovidgoyal.net> 4 5from itertools import count 6import os 7import shutil 8 9from bypy.constants import is64bit 10from bypy.utils import run 11 12WIXP = r'C:\Program Files (x86)\WiX Toolset v3.11' 13if is64bit: 14 UPGRADE_CODE = '5DD881FF-756B-4097-9D82-8C0F11D521EA' 15else: 16 UPGRADE_CODE = 'BEB2A80D-E902-4DAD-ADF9-8BD2DA42CFE1' 17MINVERHUMAN = 'Windows 8' 18calibre_constants = globals()['calibre_constants'] 19 20CANDLE = WIXP + r'\bin\candle.exe' 21LIGHT = WIXP + r'\bin\light.exe' 22j, d, a, b = os.path.join, os.path.dirname, os.path.abspath, os.path.basename 23 24 25def create_installer(env): 26 if os.path.exists(env.installer_dir): 27 shutil.rmtree(env.installer_dir) 28 os.makedirs(env.installer_dir) 29 30 with open(j(d(__file__), 'wix-template.xml'), 'rb') as f: 31 template = f.read().decode('utf-8') 32 33 components, smap = get_components_from_files(env) 34 wxs = template.format( 35 app=calibre_constants['appname'], 36 appfolder='Calibre2' if is64bit else 'Calibre', 37 version=calibre_constants['version'], 38 upgrade_code=UPGRADE_CODE, 39 ProgramFilesFolder='ProgramFiles64Folder' if is64bit else 'ProgramFilesFolder', 40 x64=' 64bit' if is64bit else '', 41 minverhuman=MINVERHUMAN, 42 minver='602', 43 fix_wix='<Custom Action="OverwriteWixSetDefaultPerMachineFolder" After="WixSetDefaultPerMachineFolder" />' if is64bit else '', 44 compression='high', 45 app_components=components, 46 exe_map=smap, 47 main_icon=j(env.src_root, 'icons', 'library.ico'), 48 viewer_icon=j(env.src_root, 'icons', 'viewer.ico'), 49 editor_icon=j(env.src_root, 'icons', 'ebook-edit.ico'), 50 web_icon=j(env.src_root, 'icons', 'web.ico'), 51 ) 52 with open(j(d(__file__), 'en-us.xml'), 'rb') as f: 53 template = f.read().decode('utf-8') 54 enus = template.format(app=calibre_constants['appname']) 55 56 enusf = j(env.installer_dir, 'en-us.wxl') 57 wxsf = j(env.installer_dir, calibre_constants['appname'] + '.wxs') 58 with open(wxsf, 'wb') as f: 59 f.write(wxs.encode('utf-8')) 60 with open(enusf, 'wb') as f: 61 f.write(enus.encode('utf-8')) 62 wixobj = j(env.installer_dir, calibre_constants['appname'] + '.wixobj') 63 arch = 'x64' if is64bit else 'x86' 64 cmd = [CANDLE, '-nologo', '-arch', arch, '-ext', 'WiXUtilExtension', '-o', wixobj, wxsf] 65 run(*cmd) 66 installer = j(env.dist, '%s%s-%s.msi' % ( 67 calibre_constants['appname'], ('-64bit' if is64bit else ''), calibre_constants['version'])) 68 license = j(env.src_root, 'LICENSE.rtf') 69 banner = j(env.src_root, 'icons', 'wix-banner.bmp') 70 dialog = j(env.src_root, 'icons', 'wix-dialog.bmp') 71 cmd = [LIGHT, '-nologo', '-ext', 'WixUIExtension', 72 '-cultures:en-us', '-loc', enusf, wixobj, 73 '-ext', 'WixUtilExtension', 74 '-o', installer, 75 '-dWixUILicenseRtf=' + license, 76 '-dWixUIBannerBmp=' + banner, 77 '-dWixUIDialogBmp=' + dialog] 78 cmd.extend([ 79 '-sice:ICE60', # No language in dlls warning 80 '-sice:ICE61', # Allow upgrading with same version number 81 '-sice:ICE40', # Re-install mode overridden 82 '-sice:ICE69', # Shortcut components are part of a different feature than the files they point to 83 ]) 84 cmd.append('-sval') # Disable all checks since they fail when running under ssh 85 run(*cmd) 86 87 88def get_components_from_files(env): 89 90 file_idc = count() 91 file_id_map = {} 92 93 def process_dir(path): 94 components = [] 95 for x in os.listdir(path): 96 f = os.path.join(path, x) 97 file_id_map[f] = fid = next(file_idc) 98 99 if os.path.isdir(f): 100 components.append( 101 '<Directory Id="file_%s" FileSource="%s" Name="%s">' % 102 (file_id_map[f], f, x)) 103 c = process_dir(f) 104 components.extend(c) 105 components.append('</Directory>') 106 else: 107 checksum = 'Checksum="yes"' if x.endswith('.exe') else '' 108 c = [ 109 ('<Component Id="component_%s" Feature="MainApplication" ' 110 'Guid="*">') % (fid,), 111 ('<File Id="file_%s" Source="%s" Name="%s" ReadOnly="yes" ' 112 'KeyPath="yes" %s/>') % 113 (fid, f, x, checksum), 114 '</Component>' 115 ] 116 if x.endswith('.exe') and not x.startswith('pdf'): 117 # Add the executable to app paths so that users can 118 # launch it from the run dialog even if it is not on 119 # the path. See http://msdn.microsoft.com/en-us/library/windows/desktop/ee872121(v=vs.85).aspx 120 c[-1:-1] = [ 121 ('<RegistryValue Root="HKLM" ' 122 r'Key="SOFTWARE\Microsoft\Windows\CurrentVersion\App ' 123 r'Paths\%s" Value="[#file_%d]" Type="string" />' % (x, fid)), 124 ('<RegistryValue Root="HKLM" ' 125 r'Key="SOFTWARE\Microsoft\Windows\CurrentVersion\App ' 126 r'Paths\{0}" Name="Path" Value="[APPLICATIONFOLDER]" ' 127 'Type="string" />'.format(x)), 128 ] 129 components.append('\n'.join(c)) 130 return components 131 132 components = process_dir(a(env.base)) 133 smap = {} 134 for x in calibre_constants['basenames']['gui']: 135 smap[x] = 'file_%d' % file_id_map[a(j(env.base, x + '.exe'))] 136 137 return '\t\t\t\t' + '\n\t\t\t\t'.join(components), smap 138