1
0
mirror of https://github.com/EDCD/EDMarketConnector.git synced 2025-04-12 15:27:14 +03:00

Build: Enforce win32 only at top-level, and clean up how error is raised

* Repeating the same test is just un-necessary, bad past-Ath.
* Use `raise AssertionError(...)`.

I'd move some of this stuff into functions, but it wouldn't actually
aid readability.
This commit is contained in:
Athanasius 2022-09-24 10:59:44 +01:00
parent 9347f12723
commit 26c2bd720f
No known key found for this signature in database
GPG Key ID: 8C392035DD80FD62

View File

@ -22,12 +22,12 @@ if sys.version_info[0:2] != (3, 10):
raise AssertionError(f'Unexpected python version {sys.version}')
if sys.platform == 'win32':
assert platform.architecture()[0] == '32bit', 'Assumes a Python built for 32bit'
assert platform.architecture()[0] == '32bit', 'A Python 32bit build is required'
import py2exe # noqa: F401 # Yes, this *is* used
dist_dir = 'dist.win32'
else:
assert False, f'Unsupported platform {sys.platform}'
raise AssertionError(f'Unsupported platform {sys.platform}')
###########################################################################
###########################################################################
@ -76,62 +76,57 @@ PLUGINS = [
'plugins/inara.py',
]
# Ensure this fails on non-win32
if sys.platform == 'win32':
OPTIONS = {
'py2exe': {
'dist_dir': dist_dir,
'optimize': 2,
'packages': [
'asyncio', # No longer auto as of py3.10+py2exe 0.11
'multiprocessing', # No longer auto as of py3.10+py2exe 0.11
'sqlite3', # Included for plugins
'util', # 2022-02-01 only imported in plugins/eddn.py
],
'includes': [
'dataclasses',
'shutil', # Included for plugins
'timeout_session',
'zipfile', # Included for plugins
],
'excludes': [
'distutils',
'_markerlib',
'optparse',
'PIL',
'simplejson',
'unittest'
],
}
OPTIONS = {
'py2exe': {
'dist_dir': dist_dir,
'optimize': 2,
'packages': [
'asyncio', # No longer auto as of py3.10+py2exe 0.11
'multiprocessing', # No longer auto as of py3.10+py2exe 0.11
'sqlite3', # Included for plugins
'util', # 2022-02-01 only imported in plugins/eddn.py
],
'includes': [
'dataclasses',
'shutil', # Included for plugins
'timeout_session',
'zipfile', # Included for plugins
],
'excludes': [
'distutils',
'_markerlib',
'optparse',
'PIL',
'simplejson',
'unittest'
],
}
}
DATA_FILES = [
('', [
'.gitversion', # Contains git short hash
'WinSparkle.dll',
'WinSparkle.pdb', # For debugging - don't include in package
'EUROCAPS.TTF',
'ChangeLog.md',
'snd_good.wav',
'snd_bad.wav',
'modules.p',
'ships.p',
f'{appname}.VisualElementsManifest.xml',
f'{appname}.ico',
'EDMarketConnector - TRACE.bat',
'EDMarketConnector - localserver-auth.bat',
'EDMarketConnector - reset-ui.bat',
]),
('L10n', [join('L10n', x) for x in os.listdir('L10n') if x.endswith('.strings')]),
('FDevIDs', [
join('FDevIDs', 'commodity.csv'),
join('FDevIDs', 'rare_commodity.csv'),
]),
('plugins', PLUGINS),
]
else:
raise AssertionError('Unsupported platform')
DATA_FILES = [
('', [
'.gitversion', # Contains git short hash
'WinSparkle.dll',
'WinSparkle.pdb', # For debugging - don't include in package
'EUROCAPS.TTF',
'ChangeLog.md',
'snd_good.wav',
'snd_bad.wav',
'modules.p',
'ships.p',
f'{appname}.VisualElementsManifest.xml',
f'{appname}.ico',
'EDMarketConnector - TRACE.bat',
'EDMarketConnector - localserver-auth.bat',
'EDMarketConnector - reset-ui.bat',
]),
('L10n', [join('L10n', x) for x in os.listdir('L10n') if x.endswith('.strings')]),
('FDevIDs', [
join('FDevIDs', 'commodity.csv'),
join('FDevIDs', 'rare_commodity.csv'),
]),
('plugins', PLUGINS),
]
###########################################################################
###########################################################################
@ -172,136 +167,131 @@ freeze(
# Build installer(s)
###########################################################################
package_filename = None
# Ensure this fails on non-win32
if sys.platform == 'win32':
template_file = pathlib.Path('wix/template.wxs')
components_file = pathlib.Path('wix/components.wxs')
final_wxs_file = pathlib.Path('EDMarketConnector.wxs')
template_file = pathlib.Path('wix/template.wxs')
components_file = pathlib.Path('wix/components.wxs')
final_wxs_file = pathlib.Path('EDMarketConnector.wxs')
# Use heat.exe to generate the Component for all files inside dist.win32
os.system(rf'"{WIXPATH}\heat.exe" dir {dist_dir}\ -ag -sfrag -srid -suid -out {components_file}')
# Use heat.exe to generate the Component for all files inside dist.win32
os.system(rf'"{WIXPATH}\heat.exe" dir {dist_dir}\ -ag -sfrag -srid -suid -out {components_file}')
component_tree = etree.parse(str(components_file))
# 1. Change the element:
#
# <Directory Id="dist.win32" Name="dist.win32">
#
# to:
#
# <Directory Id="INSTALLDIR" Name="$(var.PRODUCTNAME)">
directory_win32 = component_tree.find('.//{*}Directory[@Id="dist.win32"][@Name="dist.win32"]')
if directory_win32 is None:
raise ValueError(f'{components_file}: Expected Directory with Id="dist.win32"')
component_tree = etree.parse(str(components_file))
# 1. Change the element:
#
# <Directory Id="dist.win32" Name="dist.win32">
#
# to:
#
# <Directory Id="INSTALLDIR" Name="$(var.PRODUCTNAME)">
directory_win32 = component_tree.find('.//{*}Directory[@Id="dist.win32"][@Name="dist.win32"]')
if directory_win32 is None:
raise ValueError(f'{components_file}: Expected Directory with Id="dist.win32"')
directory_win32.set('Id', 'INSTALLDIR')
directory_win32.set('Name', '$(var.PRODUCTNAME)')
# 2. Change:
#
# <Component Id="EDMarketConnector.exe" Guid="*">
# <File Id="EDMarketConnector.exe" KeyPath="yes" Source="SourceDir\EDMarketConnector.exe" />
# </Component>
#
# to:
#
# <Component Id="MainExecutable" Guid="{D33BB66E-9664-4AB6-A044-3004B50A09B0}">
# <File Id="EDMarketConnector.exe" KeyPath="yes" Source="SourceDir\EDMarketConnector.exe" />
# <Shortcut Id="MainExeShortcut" Directory="ProgramMenuFolder" Name="$(var.PRODUCTLONGNAME)"
# Description="Downloads station data from Elite: Dangerous" WorkingDirectory="INSTALLDIR"
# Icon="EDMarketConnector.exe" IconIndex="0" Advertise="yes" />
# </Component>
main_executable = directory_win32.find('.//{*}Component[@Id="EDMarketConnector.exe"]')
if main_executable is None:
raise ValueError(f'{components_file}: Expected Component with Id="EDMarketConnector.exe"')
directory_win32.set('Id', 'INSTALLDIR')
directory_win32.set('Name', '$(var.PRODUCTNAME)')
# 2. Change:
#
# <Component Id="EDMarketConnector.exe" Guid="*">
# <File Id="EDMarketConnector.exe" KeyPath="yes" Source="SourceDir\EDMarketConnector.exe" />
# </Component>
#
# to:
#
# <Component Id="MainExecutable" Guid="{D33BB66E-9664-4AB6-A044-3004B50A09B0}">
# <File Id="EDMarketConnector.exe" KeyPath="yes" Source="SourceDir\EDMarketConnector.exe" />
# <Shortcut Id="MainExeShortcut" Directory="ProgramMenuFolder" Name="$(var.PRODUCTLONGNAME)"
# Description="Downloads station data from Elite: Dangerous" WorkingDirectory="INSTALLDIR"
# Icon="EDMarketConnector.exe" IconIndex="0" Advertise="yes" />
# </Component>
main_executable = directory_win32.find('.//{*}Component[@Id="EDMarketConnector.exe"]')
if main_executable is None:
raise ValueError(f'{components_file}: Expected Component with Id="EDMarketConnector.exe"')
main_executable.set('Id', 'MainExecutable')
main_executable.set('Guid', '{D33BB66E-9664-4AB6-A044-3004B50A09B0}')
shortcut = etree.SubElement(
main_executable,
'Shortcut',
nsmap=main_executable.nsmap,
main_executable.set('Id', 'MainExecutable')
main_executable.set('Guid', '{D33BB66E-9664-4AB6-A044-3004B50A09B0}')
shortcut = etree.SubElement(
main_executable,
'Shortcut',
nsmap=main_executable.nsmap,
attrib={
'Id': 'MainExeShortcut',
'Directory': 'ProgramMenuFolder',
'Name': '$(var.PRODUCTLONGNAME)',
'Description': 'Downloads station data from Elite: Dangerous',
'WorkingDirectory': 'INSTALLDIR',
'Icon': 'EDMarketConnector.exe',
'IconIndex': '0',
'Advertise': 'yes'
}
)
# Now insert the appropriate parts as a child of the ProgramFilesFolder part
# of the template.
template_tree = etree.parse(str(template_file))
program_files_folder = template_tree.find('.//{*}Directory[@Id="ProgramFilesFolder"]')
if program_files_folder is None:
raise ValueError(f'{template_file}: Expected Directory with Id="ProgramFilesFolder"')
program_files_folder.insert(0, directory_win32)
# Append the Feature/ComponentRef listing to match
feature = template_tree.find('.//{*}Feature[@Id="Complete"][@Level="1"]')
if feature is None:
raise ValueError(f'{template_file}: Expected Feature element with Id="Complete" Level="1"')
# This isn't part of the components
feature.append(
etree.Element(
'ComponentRef',
attrib={
'Id': 'MainExeShortcut',
'Directory': 'ProgramMenuFolder',
'Name': '$(var.PRODUCTLONGNAME)',
'Description': 'Downloads station data from Elite: Dangerous',
'WorkingDirectory': 'INSTALLDIR',
'Icon': 'EDMarketConnector.exe',
'IconIndex': '0',
'Advertise': 'yes'
}
'Id': 'RegistryEntries'
},
nsmap=directory_win32.nsmap
)
# Now insert the appropriate parts as a child of the ProgramFilesFolder part
# of the template.
template_tree = etree.parse(str(template_file))
program_files_folder = template_tree.find('.//{*}Directory[@Id="ProgramFilesFolder"]')
if program_files_folder is None:
raise ValueError(f'{template_file}: Expected Directory with Id="ProgramFilesFolder"')
program_files_folder.insert(0, directory_win32)
# Append the Feature/ComponentRef listing to match
feature = template_tree.find('.//{*}Feature[@Id="Complete"][@Level="1"]')
if feature is None:
raise ValueError(f'{template_file}: Expected Feature element with Id="Complete" Level="1"')
# This isn't part of the components
)
for c in directory_win32.findall('.//{*}Component'):
feature.append(
etree.Element(
'ComponentRef',
attrib={
'Id': 'RegistryEntries'
'Id': c.get('Id')
},
nsmap=directory_win32.nsmap
)
)
for c in directory_win32.findall('.//{*}Component'):
feature.append(
etree.Element(
'ComponentRef',
attrib={
'Id': c.get('Id')
},
nsmap=directory_win32.nsmap
)
)
# Insert what we now have into the template and write it out
template_tree.write(
str(final_wxs_file), encoding='utf-8',
pretty_print=True,
xml_declaration=True
# Insert what we now have into the template and write it out
template_tree.write(
str(final_wxs_file), encoding='utf-8',
pretty_print=True,
xml_declaration=True
)
os.system(rf'"{WIXPATH}\candle.exe" {appname}.wxs')
if not exists(f'{appname}.wixobj'):
raise AssertionError(f'No {appname}.wixobj: candle.exe failed?')
package_filename = f'{appname}_win_{appversion_nobuild()}.msi'
os.system(rf'"{WIXPATH}\light.exe" -b {dist_dir}\ -sacl -spdb -sw1076 {appname}.wixobj -out {package_filename}')
if not exists(package_filename):
raise AssertionError(f'light.exe failed, no {package_filename}')
# Seriously, this is how you make Windows Installer use the user's display language for its dialogs. What a crock.
# http://www.geektieguy.com/2010/03/13/create-a-multi-lingual-multi-language-msi-using-wix-and-custom-build-scripts
lcids = [
int(x) for x in re.search( # type: ignore
r'Languages\s*=\s*"(.+?)"',
open(f'{appname}.wxs').read()
).group(1).split(',')
]
assert lcids[0] == 1033, f'Default language is {lcids[0]}, should be 1033 (en_US)'
shutil.copyfile(package_filename, join(gettempdir(), f'{appname}_1033.msi'))
for lcid in lcids[1:]:
shutil.copyfile(
join(gettempdir(), f'{appname}_1033.msi'),
join(gettempdir(), f'{appname}_{lcid}.msi')
)
os.system(rf'"{WIXPATH}\candle.exe" {appname}.wxs')
if not exists(f'{appname}.wixobj'):
raise AssertionError(f'No {appname}.wixobj: candle.exe failed?')
package_filename = f'{appname}_win_{appversion_nobuild()}.msi'
os.system(rf'"{WIXPATH}\light.exe" -b {dist_dir}\ -sacl -spdb -sw1076 {appname}.wixobj -out {package_filename}')
if not exists(package_filename):
raise AssertionError(f'light.exe failed, no {package_filename}')
# Seriously, this is how you make Windows Installer use the user's display language for its dialogs. What a crock.
# http://www.geektieguy.com/2010/03/13/create-a-multi-lingual-multi-language-msi-using-wix-and-custom-build-scripts
lcids = [
int(x) for x in re.search( # type: ignore
r'Languages\s*=\s*"(.+?)"',
open(f'{appname}.wxs').read()
).group(1).split(',')
]
assert lcids[0] == 1033, f'Default language is {lcids[0]}, should be 1033 (en_US)'
shutil.copyfile(package_filename, join(gettempdir(), f'{appname}_1033.msi'))
for lcid in lcids[1:]:
shutil.copyfile(
join(gettempdir(), f'{appname}_1033.msi'),
join(gettempdir(), f'{appname}_{lcid}.msi')
)
# Don't care about codepage because the displayed strings come from msiexec not our msi
os.system(rf'cscript /nologo "{SDKPATH}\WiLangId.vbs" {gettempdir()}\{appname}_{lcid}.msi Product {lcid}')
os.system(rf'"{SDKPATH}\MsiTran.Exe" -g {gettempdir()}\{appname}_1033.msi {gettempdir()}\{appname}_{lcid}.msi {gettempdir()}\{lcid}.mst') # noqa: E501 # Not going to get shorter
os.system(rf'cscript /nologo "{SDKPATH}\WiSubStg.vbs" {package_filename} {gettempdir()}\{lcid}.mst {lcid}')
else:
raise AssertionError('Unsupported platform')
# Don't care about codepage because the displayed strings come from msiexec not our msi
os.system(rf'cscript /nologo "{SDKPATH}\WiLangId.vbs" {gettempdir()}\{appname}_{lcid}.msi Product {lcid}')
os.system(rf'"{SDKPATH}\MsiTran.Exe" -g {gettempdir()}\{appname}_1033.msi {gettempdir()}\{appname}_{lcid}.msi {gettempdir()}\{lcid}.mst') # noqa: E501 # Not going to get shorter
os.system(rf'cscript /nologo "{SDKPATH}\WiSubStg.vbs" {package_filename} {gettempdir()}\{lcid}.mst {lcid}')
###########################################################################