|
|
@ -102,8 +102,9 @@ from distutils import dir_util |
|
|
|
from hashlib import md5 |
|
|
|
from shutil import copy, copytree, move, rmtree |
|
|
|
|
|
|
|
from lxml.etree import fromstring, parse, tostring |
|
|
|
from lxml.etree import ElementTree |
|
|
|
from lxml.builder import E, ElementMaker |
|
|
|
from lxml.objectify import fromstring |
|
|
|
|
|
|
|
from builder import Builder |
|
|
|
|
|
|
@ -128,63 +129,81 @@ class WindowsBuilder(Builder): |
|
|
|
else: |
|
|
|
return None |
|
|
|
|
|
|
|
def _get_fragments_from_files(self, start_dir): |
|
|
|
def _get_dirs_and_files(self, install_dir, start_dir): |
|
|
|
""" |
|
|
|
Walk down a directory recursively and build up the XML for WiX |
|
|
|
""" |
|
|
|
self._openlp_id = None |
|
|
|
start_base, start_path = os.path.split(start_dir) |
|
|
|
element = E.DirectoryRef(Id='INSTALLDIR') |
|
|
|
element = install_dir |
|
|
|
directories = {start_path: {'__dir__': element}} |
|
|
|
components = [] |
|
|
|
component_ids = [] |
|
|
|
FxE = ElementMaker(namespace='http://schemas.microsoft.com/wix/FirewallExtension', |
|
|
|
nsmap={'fw': 'http://schemas.microsoft.com/wix/FirewallExtension'}) |
|
|
|
|
|
|
|
for root, _, files in os.walk(start_dir): |
|
|
|
parent = os.sep.join(root.replace(os.path.join(start_base, ''), '').split(os.sep)[:-1]) |
|
|
|
if root == start_dir: |
|
|
|
path = '' |
|
|
|
else: |
|
|
|
path = root.replace(os.path.join(start_dir, ''), '') |
|
|
|
base = os.path.basename(root) |
|
|
|
if root != start_dir: |
|
|
|
dir_id = 'dir_{parent}_{base}'.format(parent=parent.replace(os.sep, '_'), base=base) |
|
|
|
element = E.Directory(Id=dir_id, Name=base) |
|
|
|
dir_id = 'd_{}'.format(md5(os.path.join(parent, base).encode('utf8')).hexdigest()) |
|
|
|
new_element = E.Directory(Id=dir_id, Name=base) |
|
|
|
element.append(new_element) |
|
|
|
element = new_element |
|
|
|
new_dir = {'__dir__': element} |
|
|
|
parent_dir = self._walk_dirs(directories, parent) |
|
|
|
parent_dir[base] = new_dir |
|
|
|
parent_dir['__dir__'].append(element) |
|
|
|
for fname in files: |
|
|
|
source = os.path.join(path, fname) if path else fname |
|
|
|
source_id = md5(source.encode('utf8')).hexdigest() |
|
|
|
file_id = 'file_{source_id}'.format(source_id=source_id) |
|
|
|
component_id = 'cmp_{source_id}'.format(source_id=source_id) |
|
|
|
source = os.path.join(root, fname) |
|
|
|
source_id = 'f_{}'.format(md5(source.encode('utf8')).hexdigest()) |
|
|
|
component_ids.append(source_id) |
|
|
|
if self.arch == 'x64': |
|
|
|
file_ = E.File(Id=file_id, KeyPath='yes', Source=source, ProcessorArchitecture='x64') |
|
|
|
component = E.Component(file_, Id=component_id, Guid='*', Win64='yes') |
|
|
|
file_ = E.File(Id=source_id, Name=fname, Source=source, ProcessorArchitecture='x64') |
|
|
|
component = E.Component(file_, Id=source_id, Guid='*', DiskId='1', Win64='yes') |
|
|
|
else: |
|
|
|
file_ = E.File(Id=file_id, KeyPath='yes', Source=source) |
|
|
|
component = E.Component(file_, Id=component_id, Guid='*') |
|
|
|
file_ = E.File(Id=source_id, Name=fname, Source=source) |
|
|
|
component = E.Component(file_, Id=source_id, Guid='*', DiskId='1') |
|
|
|
if source.endswith('OpenLP.exe'): |
|
|
|
description = 'Firewall exception for OpenLP\'s remote interface (UDP)' |
|
|
|
program = '[#file_e368869eb54b01e2288a3359b1cf51f8]' |
|
|
|
component.append(FxE.FirewallException(Id='OpenLP_TCP', Name='OpenLP', |
|
|
|
Description=description, IgnoreFailure='yes', |
|
|
|
Program=program, Protocol='tcp', Scope='any')) |
|
|
|
component.append(FxE.FirewallException(Id='OpenLP_UDP', Name='OpenLP', |
|
|
|
Description=description, IgnoreFailure='yes', |
|
|
|
Program=program, Protocol='udp', Scope='any')) |
|
|
|
# Add the file association XML |
|
|
|
config_dir = os.path.dirname(self.config_path) |
|
|
|
with open(os.path.join(config_dir, 'file-associations.xml')) as assoc_file: |
|
|
|
file_assoc = parse(assoc_file) |
|
|
|
component.append(file_assoc.getroot()) |
|
|
|
self._openlp_id = source_id |
|
|
|
file_.set('KeyPath', 'yes') |
|
|
|
fw_program = '[#{}]'.format(source_id) |
|
|
|
component.append(FxE.FirewallException(Id='OpenLP_TCP', Name='$(var.ProductName)', |
|
|
|
IgnoreFailure='yes', Program=fw_program, |
|
|
|
Protocol='tcp', Scope='any')) |
|
|
|
component.append(FxE.FirewallException(Id='OpenLP_UDP', Name='$(var.ProductName)', |
|
|
|
IgnoreFailure='yes', Program=fw_program, |
|
|
|
Protocol='udp', Scope='any')) |
|
|
|
component.append(E.Shortcut(Id='ApplicationStartMenuShortcut', Name='$(var.ProductName)', |
|
|
|
Description='$(var.Description)', Directory='ProgramMenuDir', |
|
|
|
Icon='OpenLP.ico', Advertise='yes', WorkingDirectory='INSTALLDIR')) |
|
|
|
component.append(E.Shortcut(Id='DebugStartMenuShortcut', Name='$(var.ProductName) (Debug)', |
|
|
|
Description='Run $(var.ProductName) with debug logging enabled', |
|
|
|
Directory='ProgramMenuDir', Arguments='--log-level debug', |
|
|
|
Icon='OpenLP.ico', Advertise='yes', WorkingDirectory='INSTALLDIR')) |
|
|
|
component.append(E.ProgId( |
|
|
|
E.Extension( |
|
|
|
E.Verb(Id="Open", Command="Open", Argument=" "%1""), |
|
|
|
E.MIME(Advertise="yes", ContentType="application/-x-openlp-service", Default="yes"), |
|
|
|
Id="osz" |
|
|
|
), |
|
|
|
E.Extension( |
|
|
|
E.Verb(Id="Open", Command="Open", Argument=" "%1""), |
|
|
|
E.MIME(Advertise="yes", ContentType="application/-x-openlp-service-lite", Default="yes"), |
|
|
|
Id="oszl" |
|
|
|
), |
|
|
|
Id="OpenLP.Service", |
|
|
|
Description="OpenLP Service File", |
|
|
|
Icon="service_file.ico", |
|
|
|
Advertise="yes" |
|
|
|
)) |
|
|
|
elif source.endswith('OpenLP.chm'): |
|
|
|
component.append(E.Shortcut(Id='HelpStartMenuShortcut', Name='$(var.ProductName) Help', |
|
|
|
Description='Help file for $(var.ProductName)', |
|
|
|
Target='[#{}]'.format(source_id), WorkingDirectory='INSTALLDIR')) |
|
|
|
element.append(component) |
|
|
|
components.append(component) |
|
|
|
|
|
|
|
files_fragment = E.Fragment(directories[start_path]['__dir__']) |
|
|
|
comps_fragment = E.Fragment(E.ComponentGroup(*[E.ComponentRef(Id=c.attrib['Id']) for c in components], |
|
|
|
Id='Files')) |
|
|
|
return files_fragment, comps_fragment |
|
|
|
return component_ids |
|
|
|
|
|
|
|
def _create_wix_file(self): |
|
|
|
""" |
|
|
@ -195,27 +214,40 @@ class WindowsBuilder(Builder): |
|
|
|
self._print_verbose('Reading base WiX file') |
|
|
|
with open(os.path.join(config_dir, 'OpenLP-base.wxs'), 'rt') as base_file: |
|
|
|
xml = base_file.read() |
|
|
|
progfilefolder = 'ProgramFiles64Folder' if self.arch == 'x64' else 'ProgramFilesFolder' |
|
|
|
# convert the version string to format x.x.x if needed |
|
|
|
if '.dev' in self.version: |
|
|
|
windows_version = self.version.replace('.dev', '.') |
|
|
|
windows_version = windows_version.rsplit('+', 1)[0] |
|
|
|
else: |
|
|
|
windows_version = self.version |
|
|
|
xml = xml % dict(dialog=os.path.join(config_dir, 'WizardMain.bmp'), |
|
|
|
banner=os.path.join(config_dir, 'WizardBanner.bmp'), |
|
|
|
platform=self.arch, |
|
|
|
progfilefolder=progfilefolder, |
|
|
|
version=windows_version) |
|
|
|
tree = fromstring(xml.encode('utf8')) |
|
|
|
xml = xml % { |
|
|
|
'dialog': os.path.join(config_dir, 'WizardMain.bmp'), |
|
|
|
'banner': os.path.join(config_dir, 'WizardBanner.bmp'), |
|
|
|
'license': os.path.join(config_dir, 'LICENSE.rtf'), |
|
|
|
'platform': self.arch, |
|
|
|
'progfilefolder': 'ProgramFiles64Folder' if self.arch == 'x64' else 'ProgramFilesFolder', |
|
|
|
'systemfolder': 'System64Folder' if self.arch == 'x64' else 'SystemFolder', |
|
|
|
'version': windows_version |
|
|
|
} |
|
|
|
root = fromstring(xml.encode('utf8')) |
|
|
|
# Find the INSTALLDIR directory component and populate it with our files and folders |
|
|
|
install_dir = root.xpath('//wix:Directory[@Id="INSTALLDIR"]', |
|
|
|
namespaces={'wix': 'http://schemas.microsoft.com/wix/2006/wi'})[0] |
|
|
|
self._print_verbose('Creating XML fragments from files and directories') |
|
|
|
fragments = self._get_fragments_from_files(self.dist_path) |
|
|
|
self._print_verbose('Inserting XML fragments into base WiX file') |
|
|
|
for fragment in fragments: |
|
|
|
tree.append(fragment) |
|
|
|
component_ids = self._get_dirs_and_files(install_dir, self.dist_path) |
|
|
|
# Write the property for the "Run OpenLP" checkbox |
|
|
|
product = root.xpath('//wix:Product', |
|
|
|
namespaces={'wix': 'http://schemas.microsoft.com/wix/2006/wi'})[0] |
|
|
|
product.append(E.Property(Id='WixShellExecTarget', Value='[#{}]'.format(self._openlp_id))) |
|
|
|
# Set the component ids for the feature |
|
|
|
feature = root.xpath('//wix:Feature', |
|
|
|
namespaces={'wix': 'http://schemas.microsoft.com/wix/2006/wi'})[0] |
|
|
|
for component_id in component_ids: |
|
|
|
feature.append(E.ComponentRef(Id=component_id)) |
|
|
|
self._print_verbose('Writing new WiX file') |
|
|
|
tree = ElementTree(root) |
|
|
|
with open(os.path.join(config_dir, 'OpenLP.wxs'), 'wb') as f: |
|
|
|
f.write(tostring(tree, encoding='utf-8', xml_declaration=True, pretty_print=True)) |
|
|
|
tree.write(f, encoding='utf-8', xml_declaration=True, pretty_print=True) |
|
|
|
|
|
|
|
def _run_wix_tools(self): |
|
|
|
""" |
|
|
|