Integrate msicreator into LO and generate installer
Co-authored-by: Ximena Alcaman <alcamanximena@gmail.com> Change-Id: Iea2ea3b4bddc975a032592403727a4ff00db4a5d Reviewed-on: https://gerrit.libreoffice.org/c/core/+/146843 Tested-by: Jenkins Reviewed-by: Thorsten Behrens <thorsten.behrens@allotropia.de>
This commit is contained in:
parent
35787e07b7
commit
9aee0383c3
4 changed files with 692 additions and 0 deletions
|
@ -74,6 +74,8 @@ instsetoo_installer_targets := $(foreach pkgformat,$(PKGFORMAT),\
|
|||
$(foreach lang,$(filter-out en-US,$(gb_WITH_LANG)),ooolangpack‧$(lang)‧‧-languagepack‧$(pkgformat)‧nostrip)))
|
||||
endif
|
||||
|
||||
LIBO_VERSION = $(LIBO_VERSION_MAJOR).$(LIBO_VERSION_MINOR).$(LIBO_VERSION_MICRO).$(LIBO_VERSION_PATCH)
|
||||
|
||||
instsetoo_wipe:
|
||||
$(call gb_Output_announce,wiping installation output dir,$(true),WPE,6)
|
||||
rm -rf $(instsetoo_OUT)
|
||||
|
@ -113,6 +115,7 @@ $(instsetoo_installer_targets): $(SRCDIR)/solenv/bin/make_installer.pl \
|
|||
$(call gb_CustomTarget_get_workdir,instsetoo_native/install)/install.phony: $(instsetoo_installer_targets)
|
||||
$(call gb_Output_announce,$(subst $(WORKDIR)/,,$@),$(true),PRL,2)
|
||||
$(call gb_Trace_StartRange,$(subst $(WORKDIR)/,,$@),PRL)
|
||||
$(if $(LODE_HOME),$(call gb_ExternalExecutable_get_command,python) $(SRCDIR)/msicreator/create_installer.py $(BUILDDIR) $(SRCDIR) $(LIBO_VERSION) $(PRODUCTNAME_WITHOUT_SPACES))
|
||||
ifeq (TRUE,$(LIBO_TEST_INSTALL))
|
||||
unzip -q -d $(TESTINSTALLDIR) $(instsetoo_OUT)/$(PRODUCTNAME_WITHOUT_SPACES)/archive/install/en-US/LibreOffice*_archive.zip
|
||||
mv $(TESTINSTALLDIR)/LibreOffice*_archive/LibreOffice*/* $(TESTINSTALLDIR)/
|
||||
|
|
Binary file not shown.
After Width: | Height: | Size: 12 KiB |
84
msicreator/create_installer.py
Normal file
84
msicreator/create_installer.py
Normal file
|
@ -0,0 +1,84 @@
|
|||
import os, sys
|
||||
from shutil import copytree, copy2, move, rmtree
|
||||
import json
|
||||
import createmsi
|
||||
|
||||
build_dir = sys.argv[1]
|
||||
src_dir = sys.argv[2]
|
||||
creator_dir = os.path.join(build_dir, 'workdir/installation/MSICreatorLO')
|
||||
|
||||
def prepare_project_dir():
|
||||
instdir = os.path.join(build_dir, 'instdir')
|
||||
fonts_dir = os.path.join(instdir, 'share/fonts')
|
||||
new_fonts_dir = os.path.join(creator_dir, 'libo-fonts/share/fonts')
|
||||
main_dir = os.path.join(creator_dir, 'main')
|
||||
src_uninstaller_icon = os.path.join(src_dir, 'icon-themes/colibre/res/mainapp_48_8.png')
|
||||
src_ui_banner = os.path.join(src_dir, 'instsetoo_native/inc_common/windows/msi_templates/Binary/Banner.bmp')
|
||||
src_ui_background = os.path.join(src_dir, 'instsetoo_native/inc_common/windows/msi_templates/Binary/Image_2.bmp')
|
||||
graphics_dir = os.path.join(creator_dir, 'graphics')
|
||||
sdk_dir = os.path.join(creator_dir, 'main/sdk')
|
||||
try:
|
||||
move(fonts_dir, new_fonts_dir)
|
||||
copytree(instdir, main_dir, dirs_exist_ok=True)
|
||||
copy2(src_uninstaller_icon, creator_dir)
|
||||
os.mkdir(graphics_dir)
|
||||
copy2(src_ui_banner, graphics_dir)
|
||||
copy2(src_ui_background, graphics_dir)
|
||||
rmtree(sdk_dir)
|
||||
except FileExistsError as err:
|
||||
print(err)
|
||||
|
||||
def create_creator_json():
|
||||
lo_version = sys.argv[3]
|
||||
lo_name = sys.argv[4]
|
||||
uninstaller_icon = 'mainapp_48_8.png'
|
||||
lo_dictionary = {
|
||||
"upgrade_guid": "6f05ed48-a735-4155-ab60-e4cc98455262",
|
||||
"version": lo_version,
|
||||
"product_name": lo_name,
|
||||
"manufacturer": "The Document Foundation",
|
||||
"name": lo_name,
|
||||
"name_base": lo_name,
|
||||
"comments": "Testing a libo installer",
|
||||
"installdir": "LibreOffice Test",
|
||||
"startmenu_shortcut": "program/soffice.exe",
|
||||
"desktop_shortcut": "program/soffice.exe",
|
||||
"addremove_icon": uninstaller_icon,
|
||||
"major_upgrade": {
|
||||
"AllowDowngrades": "yes",
|
||||
"IgnoreRemoveFailure": "yes"
|
||||
},
|
||||
"graphics": {
|
||||
"banner": "graphics/Banner.bmp",
|
||||
"background": "graphics/Image_2.bmp"
|
||||
},
|
||||
"parts": [
|
||||
{
|
||||
"id": "libreoffice",
|
||||
"title": "The LibreOffice Suite",
|
||||
"description": "This is a test for the LibreOffice installer",
|
||||
"absent": "disallow",
|
||||
"staged_dir": "main"
|
||||
},
|
||||
{
|
||||
"id": "libreofficefonts",
|
||||
"title": "The LibreOffice Fonts ",
|
||||
"description": "This is a test for the LibreOffice Fonts",
|
||||
"absent": "allow",
|
||||
"staged_dir": "libo-fonts"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
lo_object = json.dumps(lo_dictionary, indent=4)
|
||||
with open(os.path.join(creator_dir, 'lo.json'), 'w') as lo_json:
|
||||
lo_json.write(lo_object)
|
||||
|
||||
def generate_installer():
|
||||
os.chdir(creator_dir)
|
||||
createmsi.run(['lo.json'])
|
||||
|
||||
if __name__ == '__main__':
|
||||
prepare_project_dir()
|
||||
create_creator_json()
|
||||
generate_installer()
|
605
msicreator/createmsi.py
Normal file
605
msicreator/createmsi.py
Normal file
|
@ -0,0 +1,605 @@
|
|||
#!/usr/bin/env python3
|
||||
|
||||
# Copyright 2017-2018 Jussi Pakkanen et al
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
import sys, os, subprocess, shutil, uuid, json, re
|
||||
from glob import glob
|
||||
import platform
|
||||
import xml.etree.ElementTree as ET
|
||||
|
||||
sys.path.append(os.getcwd())
|
||||
|
||||
def gen_guid():
|
||||
return str(uuid.uuid4()).upper()
|
||||
|
||||
class Node:
|
||||
def __init__(self, dirs, files):
|
||||
assert(isinstance(dirs, list))
|
||||
assert(isinstance(files, list))
|
||||
self.dirs = dirs
|
||||
self.files = files
|
||||
|
||||
class UIGraphics:
|
||||
def __init__(self):
|
||||
self.banner = None
|
||||
self.background = None
|
||||
|
||||
class PackageGenerator:
|
||||
|
||||
def __init__(self, jsonfile):
|
||||
jsondata = json.load(open(jsonfile, 'rb'))
|
||||
self.product_name = jsondata['product_name']
|
||||
self.manufacturer = jsondata['manufacturer']
|
||||
self.version = jsondata['version']
|
||||
self.comments = jsondata['comments']
|
||||
self.installdir = jsondata['installdir']
|
||||
self.license_file = jsondata.get('license_file', None)
|
||||
self.name = jsondata['name']
|
||||
self.guid = jsondata.get('product_guid', '*')
|
||||
self.upgrade_guid = jsondata['upgrade_guid']
|
||||
self.basename = jsondata['name_base']
|
||||
self.need_msvcrt = jsondata.get('need_msvcrt', False)
|
||||
self.addremove_icon = jsondata.get('addremove_icon', None)
|
||||
self.startmenu_shortcut = jsondata.get('startmenu_shortcut', None)
|
||||
self.desktop_shortcut = jsondata.get('desktop_shortcut', None)
|
||||
self.main_xml = self.basename + '.wxs'
|
||||
self.main_o = self.basename + '.wixobj'
|
||||
self.idnum = 0
|
||||
self.graphics = UIGraphics()
|
||||
if 'graphics' in jsondata:
|
||||
if 'banner' in jsondata['graphics']:
|
||||
self.graphics.banner = jsondata['graphics']['banner']
|
||||
if 'background' in jsondata['graphics']:
|
||||
self.graphics.background = jsondata['graphics']['background']
|
||||
if 'arch' in jsondata:
|
||||
self.arch = jsondata['arch']
|
||||
else:
|
||||
# rely on the environment variable since python architecture may not be the same as system architecture
|
||||
if 'PROGRAMFILES(X86)' in os.environ:
|
||||
self.arch = 64
|
||||
else:
|
||||
self.arch = 32 if '32' in platform.architecture()[0] else 64
|
||||
self.final_output = '%s-%s-%d.msi' % (self.basename, self.version, self.arch)
|
||||
if self.arch == 64:
|
||||
self.progfile_dir = 'ProgramFiles64Folder'
|
||||
if platform.system() == "Windows":
|
||||
redist_glob = 'C:\\Program Files\\Microsoft Visual Studio\\*\\*\\VC\\Redist\\MSVC\\v*\\MergeModules\\Microsoft_VC*_CRT_x64.msm'
|
||||
else:
|
||||
redist_glob = '/usr/share/msicreator/Microsoft_VC141_CRT_x64.msm'
|
||||
else:
|
||||
self.progfile_dir = 'ProgramFilesFolder'
|
||||
if platform.system() == "Windows":
|
||||
redist_glob = 'C:\\Program Files\\Microsoft Visual Studio\\*\\Community\\VC\\Redist\\MSVC\\*\\MergeModules\\Microsoft_VC*_CRT_x86.msm'
|
||||
else:
|
||||
redist_glob = '/usr/share/msicreator/Microsoft_VC141_CRT_x86.msm'
|
||||
trials = glob(redist_glob)
|
||||
if self.need_msvcrt:
|
||||
if len(trials) > 1:
|
||||
sys.exit('There are more than one redist dirs: ' +
|
||||
', '.join(trials))
|
||||
if len(trials) == 0:
|
||||
sys.exit('No redist dirs were detected, install MSM redistributables with VS installer.')
|
||||
self.redist_path = trials[0]
|
||||
self.component_num = 0
|
||||
self.registry_entries = jsondata.get('registry_entries', None)
|
||||
self.major_upgrade = jsondata.get('major_upgrade', None)
|
||||
self.parts = jsondata['parts']
|
||||
self.feature_components = {}
|
||||
self.feature_properties = {}
|
||||
|
||||
def generate_files(self):
|
||||
self.root = ET.Element('Wix', {'xmlns': 'http://schemas.microsoft.com/wix/2006/wi'})
|
||||
product = ET.SubElement(self.root, 'Product', {
|
||||
'Name': self.product_name,
|
||||
'Manufacturer': self.manufacturer,
|
||||
'Id': self.guid,
|
||||
'UpgradeCode': self.upgrade_guid,
|
||||
'Language': '1033',
|
||||
'Codepage': '1252',
|
||||
'Version': self.version,
|
||||
})
|
||||
|
||||
package = ET.SubElement(product, 'Package', {
|
||||
'Id': '*',
|
||||
'Keywords': 'Installer',
|
||||
'Description': '%s %s installer' % (self.name, self.version),
|
||||
'Comments': self.comments,
|
||||
'Manufacturer': self.manufacturer,
|
||||
'InstallerVersion': '500',
|
||||
'Languages': '1033',
|
||||
'Compressed': 'yes',
|
||||
'SummaryCodepage': '1252',
|
||||
})
|
||||
|
||||
if self.major_upgrade is not None:
|
||||
majorupgrade = ET.SubElement(product, 'MajorUpgrade', {})
|
||||
for mkey in self.major_upgrade.keys():
|
||||
majorupgrade.set(mkey, self.major_upgrade[mkey])
|
||||
else:
|
||||
ET.SubElement(product, 'MajorUpgrade', {'DowngradeErrorMessage': 'A newer version of %s is already installed.' % self.name})
|
||||
if self.arch == 64:
|
||||
package.set('Platform', 'x64')
|
||||
ET.SubElement(product, 'Media', {
|
||||
'Id': '1',
|
||||
'Cabinet': self.basename + '.cab',
|
||||
'EmbedCab': 'yes',
|
||||
})
|
||||
targetdir = ET.SubElement(product, 'Directory', {
|
||||
'Id': 'TARGETDIR',
|
||||
'Name': 'SourceDir',
|
||||
})
|
||||
progfiledir = ET.SubElement(targetdir, 'Directory', {
|
||||
'Id': self.progfile_dir,
|
||||
})
|
||||
pmf = ET.SubElement(targetdir, 'Directory', {'Id': 'ProgramMenuFolder'},)
|
||||
if self.startmenu_shortcut is not None:
|
||||
ET.SubElement(pmf, 'Directory', {
|
||||
'Id': 'ApplicationProgramsFolder',
|
||||
'Name': self.product_name,
|
||||
})
|
||||
if self.desktop_shortcut is not None:
|
||||
ET.SubElement(pmf, 'Directory', {'Id': 'DesktopFolder',
|
||||
'Name': 'Desktop',
|
||||
})
|
||||
installdir = ET.SubElement(progfiledir, 'Directory', {
|
||||
'Id': 'INSTALLDIR',
|
||||
'Name': self.installdir,
|
||||
})
|
||||
if self.need_msvcrt:
|
||||
ET.SubElement(installdir, 'Merge', {
|
||||
'Id': 'VCRedist',
|
||||
'SourceFile': self.redist_path,
|
||||
'DiskId': '1',
|
||||
'Language': '0',
|
||||
})
|
||||
|
||||
if self.startmenu_shortcut is not None:
|
||||
ap = ET.SubElement(product, 'DirectoryRef', {'Id': 'ApplicationProgramsFolder'})
|
||||
comp = ET.SubElement(ap, 'Component', {'Id': 'ApplicationShortcut',
|
||||
'Guid': gen_guid(),
|
||||
})
|
||||
ET.SubElement(comp, 'Shortcut', {'Id': 'ApplicationStartMenuShortcut',
|
||||
'Name': self.product_name,
|
||||
'Description': self.comments,
|
||||
'Target': '[INSTALLDIR]' + self.startmenu_shortcut,
|
||||
'WorkingDirectory': 'INSTALLDIR',
|
||||
})
|
||||
ET.SubElement(comp, 'RemoveFolder', {'Id': 'RemoveApplicationProgramsFolder',
|
||||
'Directory': 'ApplicationProgramsFolder',
|
||||
'On': 'uninstall',
|
||||
})
|
||||
ET.SubElement(comp, 'RegistryValue', {'Root': 'HKCU',
|
||||
'Key': 'Software\\Microsoft\\' + self.name,
|
||||
'Name': 'Installed',
|
||||
'Type': 'integer',
|
||||
'Value': '1',
|
||||
'KeyPath': 'yes',
|
||||
})
|
||||
if self.desktop_shortcut is not None:
|
||||
desk = ET.SubElement(product, 'DirectoryRef', {'Id': 'DesktopFolder'})
|
||||
comp = ET.SubElement(desk, 'Component', {'Id':'ApplicationShortcutDesktop',
|
||||
'Guid': gen_guid(),
|
||||
})
|
||||
ET.SubElement(comp, 'Shortcut', {'Id': 'ApplicationDesktopShortcut',
|
||||
'Name': self.product_name,
|
||||
'Description': self.comments,
|
||||
'Target': '[INSTALLDIR]' + self.desktop_shortcut,
|
||||
'WorkingDirectory': 'INSTALLDIR',
|
||||
})
|
||||
ET.SubElement(comp, 'RemoveFolder', {'Id': 'RemoveDesktopFolder',
|
||||
'Directory': 'DesktopFolder',
|
||||
'On': 'uninstall',
|
||||
})
|
||||
ET.SubElement(comp, 'RegistryValue', {'Root': 'HKCU',
|
||||
'Key': 'Software\\Microsoft\\' + self.name,
|
||||
'Name': 'Installed',
|
||||
'Type': 'integer',
|
||||
'Value': '1',
|
||||
'KeyPath': 'yes',
|
||||
})
|
||||
|
||||
ET.SubElement(product, 'Property', {
|
||||
'Id': 'WIXUI_INSTALLDIR',
|
||||
'Value': 'INSTALLDIR',
|
||||
})
|
||||
if platform.system() == "Windows":
|
||||
if self.license_file:
|
||||
ET.SubElement(product, 'UIRef', {
|
||||
'Id': 'WixUI_FeatureTree',
|
||||
})
|
||||
else:
|
||||
self.create_licenseless_dialog_entries(product)
|
||||
|
||||
if self.graphics.banner is not None:
|
||||
ET.SubElement(product, 'WixVariable', {
|
||||
'Id': 'WixUIBannerBmp',
|
||||
'Value': self.graphics.banner,
|
||||
})
|
||||
if self.graphics.background is not None:
|
||||
ET.SubElement(product, 'WixVariable', {
|
||||
'Id': 'WixUIDialogBmp',
|
||||
'Value': self.graphics.background,
|
||||
})
|
||||
|
||||
top_feature = ET.SubElement(product, 'Feature', {
|
||||
'Id': 'Complete',
|
||||
'Title': self.name + ' ' + self.version,
|
||||
'Description': 'The complete package',
|
||||
'Display': 'expand',
|
||||
'Level': '1',
|
||||
'ConfigurableDirectory': 'INSTALLDIR',
|
||||
})
|
||||
|
||||
for f in self.parts:
|
||||
self.scan_feature(top_feature, installdir, 1, f)
|
||||
|
||||
if self.need_msvcrt:
|
||||
vcredist_feature = ET.SubElement(top_feature, 'Feature', {
|
||||
'Id': 'VCRedist',
|
||||
'Title': 'Visual C++ runtime',
|
||||
'AllowAdvertise': 'no',
|
||||
'Display': 'hidden',
|
||||
'Level': '1',
|
||||
})
|
||||
ET.SubElement(vcredist_feature, 'MergeRef', {'Id': 'VCRedist'})
|
||||
if self.startmenu_shortcut is not None:
|
||||
ET.SubElement(top_feature, 'ComponentRef', {'Id': 'ApplicationShortcut'})
|
||||
if self.desktop_shortcut is not None:
|
||||
ET.SubElement(top_feature, 'ComponentRef', {'Id': 'ApplicationShortcutDesktop'})
|
||||
if self.addremove_icon is not None:
|
||||
icoid = 'addremoveicon.ico'
|
||||
ET.SubElement(product, 'Icon', {'Id': icoid,
|
||||
'SourceFile': self.addremove_icon,
|
||||
})
|
||||
ET.SubElement(product, 'Property', {'Id': 'ARPPRODUCTICON',
|
||||
'Value': icoid,
|
||||
})
|
||||
|
||||
if self.registry_entries is not None:
|
||||
registry_entries_directory = ET.SubElement(product, 'DirectoryRef', {'Id': 'TARGETDIR'})
|
||||
registry_entries_component = ET.SubElement(registry_entries_directory, 'Component', {'Id': 'RegistryEntries', 'Guid': gen_guid()})
|
||||
if self.arch == 64:
|
||||
registry_entries_component.set('Win64', 'yes')
|
||||
ET.SubElement(top_feature, 'ComponentRef', {'Id': 'RegistryEntries'})
|
||||
for r in self.registry_entries:
|
||||
self.create_registry_entries(registry_entries_component, r)
|
||||
|
||||
ET.ElementTree(self.root).write(self.main_xml, encoding='utf-8', xml_declaration=True)
|
||||
# ElementTree can not do prettyprinting so do it manually
|
||||
import xml.dom.minidom
|
||||
doc = xml.dom.minidom.parse(self.main_xml)
|
||||
with open(self.main_xml, 'w') as of:
|
||||
of.write(doc.toprettyxml(indent=' '))
|
||||
|
||||
def create_registry_entries(self, comp, reg):
|
||||
reg_key = ET.SubElement(comp, 'RegistryKey', {
|
||||
'Root': reg['root'],
|
||||
'Key': reg['key'],
|
||||
'Action': reg['action'],
|
||||
})
|
||||
ET.SubElement(reg_key, 'RegistryValue', {
|
||||
'Name': reg['name'],
|
||||
'Type': reg['type'],
|
||||
'Value': reg['value'],
|
||||
'KeyPath': reg['key_path'],
|
||||
})
|
||||
|
||||
def scan_feature(self, top_feature, installdir, depth, feature):
|
||||
for sd in [feature['staged_dir']]:
|
||||
if '/' in sd or '\\' in sd:
|
||||
sys.exit('Staged_dir %s must not have a path segment.' % sd)
|
||||
nodes = {}
|
||||
for root, dirs, files in os.walk(sd):
|
||||
cur_node = Node(dirs, files)
|
||||
nodes[root] = cur_node
|
||||
fdict = {
|
||||
'Id': feature['id'],
|
||||
'Title': feature['title'],
|
||||
'Description': feature['description'],
|
||||
'Level': '1'
|
||||
}
|
||||
if feature.get('absent', 'ab') == 'disallow':
|
||||
fdict['Absent'] = 'disallow'
|
||||
self.feature_properties[sd] = fdict
|
||||
|
||||
self.feature_components[sd] = []
|
||||
self.create_xml(nodes, sd, installdir, sd)
|
||||
self.build_features(nodes, top_feature, sd)
|
||||
|
||||
def build_features(self, nodes, top_feature, staging_dir):
|
||||
feature = ET.SubElement(top_feature, 'Feature', self.feature_properties[staging_dir])
|
||||
for component_id in self.feature_components[staging_dir]:
|
||||
ET.SubElement(feature, 'ComponentRef', {
|
||||
'Id': component_id,
|
||||
})
|
||||
|
||||
def path_to_id(self, pathname):
|
||||
#return re.sub(r'[^a-zA-Z0-9_.]', '_', str(pathname))[-72:]
|
||||
idstr = f'pathid{self.idnum}'
|
||||
self.idnum += 1
|
||||
return idstr
|
||||
|
||||
def create_xml(self, nodes, current_dir, parent_xml_node, staging_dir):
|
||||
cur_node = nodes[current_dir]
|
||||
if cur_node.files:
|
||||
component_id = 'ApplicationFiles%d' % self.component_num
|
||||
comp_xml_node = ET.SubElement(parent_xml_node, 'Component', {
|
||||
'Id': component_id,
|
||||
'Guid': gen_guid(),
|
||||
})
|
||||
self.feature_components[staging_dir].append(component_id)
|
||||
if self.arch == 64:
|
||||
comp_xml_node.set('Win64', 'yes')
|
||||
if platform.system() == "Windows" and self.component_num == 0:
|
||||
ET.SubElement(comp_xml_node, 'Environment', {
|
||||
'Id': 'Environment',
|
||||
'Name': 'PATH',
|
||||
'Part': 'last',
|
||||
'System': 'yes',
|
||||
'Action': 'set',
|
||||
'Value': '[INSTALLDIR]',
|
||||
})
|
||||
self.component_num += 1
|
||||
for f in cur_node.files:
|
||||
file_id = self.path_to_id(os.path.join(current_dir, f))
|
||||
ET.SubElement(comp_xml_node, 'File', {
|
||||
'Id': file_id,
|
||||
'Name': f,
|
||||
'Source': os.path.join(current_dir, f),
|
||||
})
|
||||
|
||||
for dirname in cur_node.dirs:
|
||||
dir_id = self.path_to_id(os.path.join(current_dir, dirname))
|
||||
dir_node = ET.SubElement(parent_xml_node, 'Directory', {
|
||||
'Id': dir_id,
|
||||
'Name': dirname,
|
||||
})
|
||||
self.create_xml(nodes, os.path.join(current_dir, dirname), dir_node, staging_dir)
|
||||
|
||||
def create_licenseless_dialog_entries(self, product_element):
|
||||
ui = ET.SubElement(product_element, 'UI', {
|
||||
'Id': 'WixUI_FeatureTree'
|
||||
})
|
||||
|
||||
ET.SubElement(ui, 'TextStyle', {
|
||||
'Id': 'WixUI_Font_Normal',
|
||||
'FaceName': 'Tahoma',
|
||||
'Size': '8'
|
||||
})
|
||||
|
||||
ET.SubElement(ui, 'TextStyle', {
|
||||
'Id': 'WixUI_Font_Bigger',
|
||||
'FaceName': 'Tahoma',
|
||||
'Size': '12'
|
||||
})
|
||||
|
||||
ET.SubElement(ui, 'TextStyle', {
|
||||
'Id': 'WixUI_Font_Title',
|
||||
'FaceName': 'Tahoma',
|
||||
'Size': '9',
|
||||
'Bold': 'yes'
|
||||
})
|
||||
|
||||
ET.SubElement(ui, 'Property', {
|
||||
'Id': 'DefaultUIFont',
|
||||
'Value': 'WixUI_Font_Normal'
|
||||
})
|
||||
|
||||
ET.SubElement(ui, 'Property', {
|
||||
'Id': 'WixUI_Mode',
|
||||
'Value': 'FeatureTree'
|
||||
})
|
||||
|
||||
ET.SubElement(ui, 'DialogRef', {
|
||||
'Id': 'ErrorDlg'
|
||||
})
|
||||
|
||||
ET.SubElement(ui, 'DialogRef', {
|
||||
'Id': 'FatalError'
|
||||
})
|
||||
|
||||
ET.SubElement(ui, 'DialogRef', {
|
||||
'Id': 'FilesInUse'
|
||||
})
|
||||
|
||||
ET.SubElement(ui, 'DialogRef', {
|
||||
'Id': 'MsiRMFilesInUse'
|
||||
})
|
||||
|
||||
ET.SubElement(ui, 'DialogRef', {
|
||||
'Id': 'PrepareDlg'
|
||||
})
|
||||
|
||||
ET.SubElement(ui, 'DialogRef', {
|
||||
'Id': 'ProgressDlg'
|
||||
})
|
||||
|
||||
ET.SubElement(ui, 'DialogRef', {
|
||||
'Id': 'ResumeDlg'
|
||||
})
|
||||
|
||||
ET.SubElement(ui, 'DialogRef', {
|
||||
'Id': 'UserExit'
|
||||
})
|
||||
|
||||
pub_exit = ET.SubElement(ui, 'Publish', {
|
||||
'Dialog': 'ExitDialog',
|
||||
'Control': 'Finish',
|
||||
'Event': 'EndDialog',
|
||||
'Value': 'Return',
|
||||
'Order': '999'
|
||||
})
|
||||
|
||||
pub_exit.text = '1'
|
||||
|
||||
pub_welcome_next = ET.SubElement(ui, 'Publish', {
|
||||
'Dialog': 'WelcomeDlg',
|
||||
'Control': 'Next',
|
||||
'Event': 'NewDialog',
|
||||
'Value': 'CustomizeDlg'
|
||||
})
|
||||
|
||||
pub_welcome_next.text = 'NOT Installed'
|
||||
|
||||
pub_welcome_maint_next = ET.SubElement(ui, 'Publish', {
|
||||
'Dialog': 'WelcomeDlg',
|
||||
'Control': 'Next',
|
||||
'Event': 'NewDialog',
|
||||
'Value': 'VerifyReadyDlg'
|
||||
})
|
||||
|
||||
pub_welcome_maint_next.text = 'Installed AND PATCH'
|
||||
|
||||
pub_customize_back_maint = ET.SubElement(ui, 'Publish', {
|
||||
'Dialog': 'CustomizeDlg',
|
||||
'Control': 'Back',
|
||||
'Event': 'NewDialog',
|
||||
'Value': 'MaintenanceTypeDlg',
|
||||
'Order': '1'
|
||||
})
|
||||
|
||||
pub_customize_back_maint.text = 'Installed'
|
||||
|
||||
pub_customize_back_welcome = ET.SubElement(ui, 'Publish', {
|
||||
'Dialog': 'CustomizeDlg',
|
||||
'Control': 'Back',
|
||||
'Event': 'NewDialog',
|
||||
'Value': 'WelcomeDlg',
|
||||
'Order': '2'
|
||||
})
|
||||
|
||||
pub_customize_back_welcome.text = 'Not Installed'
|
||||
|
||||
pub_customize_next = ET.SubElement(ui, 'Publish', {
|
||||
'Dialog': 'CustomizeDlg',
|
||||
'Control': 'Next',
|
||||
'Event': 'NewDialog',
|
||||
'Value': 'VerifyReadyDlg'
|
||||
})
|
||||
|
||||
pub_customize_next.text = '1'
|
||||
|
||||
pub_verify_customize_back = ET.SubElement(ui, 'Publish', {
|
||||
'Dialog': 'VerifyReadyDlg',
|
||||
'Control': 'Back',
|
||||
'Event': 'NewDialog',
|
||||
'Value': 'CustomizeDlg',
|
||||
'Order': '1'
|
||||
})
|
||||
|
||||
pub_verify_customize_back.text = 'NOT Installed OR WixUI_InstallMode = "Change"'
|
||||
|
||||
pub_verify_maint_back = ET.SubElement(ui, 'Publish', {
|
||||
'Dialog': 'VerifyReadyDlg',
|
||||
'Control': 'Back',
|
||||
'Event': 'NewDialog',
|
||||
'Value': 'MaintenanceTypeDlg',
|
||||
'Order': '2'
|
||||
})
|
||||
|
||||
pub_verify_maint_back.text = 'Installed AND NOT PATCH'
|
||||
|
||||
pub_verify_welcome_back = ET.SubElement(ui, 'Publish', {
|
||||
'Dialog': 'VerifyReadyDlg',
|
||||
'Control': 'Back',
|
||||
'Event': 'NewDialog',
|
||||
'Value': 'WelcomeDlg',
|
||||
'Order': '3'
|
||||
})
|
||||
|
||||
pub_verify_welcome_back.text = 'Installed AND PATCH'
|
||||
|
||||
pub_maint_welcome_next = ET.SubElement(ui, 'Publish', {
|
||||
'Dialog': 'MaintenanceWelcomeDlg',
|
||||
'Control': 'Next',
|
||||
'Event': 'NewDialog',
|
||||
'Value': 'MaintenanceTypeDlg'
|
||||
})
|
||||
|
||||
pub_maint_welcome_next.text = '1'
|
||||
|
||||
pub_maint_type_change = ET.SubElement(ui, 'Publish', {
|
||||
'Dialog': 'MaintenanceTypeDlg',
|
||||
'Control': 'ChangeButton',
|
||||
'Event': 'NewDialog',
|
||||
'Value': 'CustomizeDlg'
|
||||
})
|
||||
|
||||
pub_maint_type_change.text = '1'
|
||||
|
||||
pub_maint_type_repair = ET.SubElement(ui, 'Publish', {
|
||||
'Dialog': 'MaintenanceTypeDlg',
|
||||
'Control': 'RepairButton',
|
||||
'Event': 'NewDialog',
|
||||
'Value': 'VerifyReadyDlg'
|
||||
})
|
||||
|
||||
pub_maint_type_repair.text = '1'
|
||||
|
||||
pub_maint_type_remove = ET.SubElement(ui, 'Publish', {
|
||||
'Dialog': 'MaintenanceTypeDlg',
|
||||
'Control': 'RemoveButton',
|
||||
'Event': 'NewDialog',
|
||||
'Value': 'VerifyReadyDlg'
|
||||
})
|
||||
|
||||
pub_maint_type_remove.text = '1'
|
||||
|
||||
pub_maint_type_back = ET.SubElement(ui, 'Publish', {
|
||||
'Dialog': 'MaintenanceTypeDlg',
|
||||
'Control': 'Back',
|
||||
'Event': 'NewDialog',
|
||||
'Value': 'MaintenanceWelcomeDlg'
|
||||
})
|
||||
|
||||
pub_maint_type_back.text = '1'
|
||||
|
||||
ET.SubElement(product_element, 'UIRef', {
|
||||
'Id': 'WixUI_Common',
|
||||
})
|
||||
|
||||
def build_package(self):
|
||||
wixdir = 'c:\\Program Files\\Wix Toolset v3.11\\bin'
|
||||
if platform.system() != "Windows":
|
||||
wixdir = '/usr/bin'
|
||||
if not os.path.isdir(wixdir):
|
||||
wixdir = 'c:\\Program Files (x86)\\Wix Toolset v3.11\\bin'
|
||||
if not os.path.isdir(wixdir):
|
||||
print("ERROR: This script requires WIX")
|
||||
sys.exit(1)
|
||||
if platform.system() == "Windows":
|
||||
subprocess.check_call([os.path.join(wixdir, 'candle'), self.main_xml])
|
||||
subprocess.check_call([os.path.join(wixdir, 'light'),
|
||||
'-ext', 'WixUIExtension',
|
||||
'-cultures:en-us',
|
||||
'-dWixUILicenseRtf=' + self.license_file if self.license_file else '',
|
||||
'-dcl:high',
|
||||
'-out', self.final_output,
|
||||
self.main_o])
|
||||
else:
|
||||
subprocess.check_call([os.path.join(wixdir, 'wixl'), '-o', self.final_output, self.main_xml])
|
||||
|
||||
def run(args):
|
||||
if len(args) != 1:
|
||||
sys.exit('createmsi.py <msi definition json>')
|
||||
jsonfile = args[0]
|
||||
if '/' in jsonfile or '\\' in jsonfile:
|
||||
sys.exit('Input file %s must not contain a path segment.' % jsonfile)
|
||||
p = PackageGenerator(jsonfile)
|
||||
p.generate_files()
|
||||
p.build_package()
|
||||
|
||||
if __name__ == '__main__':
|
||||
run(sys.argv[1:])
|
Loading…
Reference in a new issue