mirror of
https://github.com/EDCD/EDMarketConnector.git
synced 2025-04-12 07:20:02 +03:00
#2040 First Pass - Update Build System
This commit is contained in:
parent
e41d484468
commit
247d632cc6
6
.github/workflows/windows-build.yml
vendored
6
.github/workflows/windows-build.yml
vendored
@ -43,7 +43,7 @@ jobs:
|
||||
--exclude=.git* \
|
||||
--exclude=.mypy.ini \
|
||||
--exclude=.pre-commit-config.yaml \
|
||||
--exclude=Build-exe-and-msi.py \
|
||||
--exclude=build.py \
|
||||
--exclude=*.manifest \
|
||||
--exclude=coriolis-data \
|
||||
--exclude=img \
|
||||
@ -92,7 +92,7 @@ jobs:
|
||||
# directory.
|
||||
# NB: If this gets too long it can cause zip 'Command Line Error',
|
||||
# presumably due to a Windows CL length limit.
|
||||
exclusions: 'EDMarketConnector/EDMarketConnector-release-*.* EDMarketConnector/.editorconfig EDMarketConnector/.flake8 EDMarketConnector/.git* EDMarketConnector/.mypy.ini EDMarketConnector/.pre-commit-config.yaml EDMarketConnector/Build-exe-and-msi.py EDMarketConnector/*.manifest EDMarketConnector/coriolis-data/ EDMarketConnector/img/ EDMarketConnector/pyproject.toml EDMarketConnector/scripts/ EDMarketConnector/tests/ EDMarketConnector/wix/'
|
||||
exclusions: 'EDMarketConnector/EDMarketConnector-release-*.* EDMarketConnector/.editorconfig EDMarketConnector/.flake8 EDMarketConnector/.git* EDMarketConnector/.mypy.ini EDMarketConnector/.pre-commit-config.yaml EDMarketConnector/build.py EDMarketConnector/*.manifest EDMarketConnector/coriolis-data/ EDMarketConnector/img/ EDMarketConnector/pyproject.toml EDMarketConnector/scripts/ EDMarketConnector/tests/ EDMarketConnector/wix/'
|
||||
|
||||
- uses: actions/setup-python@v4
|
||||
with:
|
||||
@ -112,7 +112,7 @@ jobs:
|
||||
|
||||
- name: Build EDMC
|
||||
run: |
|
||||
python Build-exe-and-msi.py
|
||||
python build.py
|
||||
|
||||
- name: Upload build files
|
||||
uses: actions/upload-artifact@v3
|
||||
|
@ -1,298 +0,0 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Build to executables and MSI installer using py2exe and other tools."""
|
||||
import os
|
||||
import pathlib
|
||||
import re
|
||||
import shutil
|
||||
import sys
|
||||
from os.path import exists, isdir, join
|
||||
from tempfile import gettempdir
|
||||
|
||||
from lxml import etree
|
||||
from py2exe import freeze
|
||||
|
||||
from config import appcmdname, appname, appversion, appversion_nobuild, copyright, git_shorthash_from_head
|
||||
from constants import GITVERSION_FILE
|
||||
|
||||
###########################################################################
|
||||
# Check we're on a supported platform
|
||||
###########################################################################
|
||||
if sys.version_info[0:2] != (3, 11):
|
||||
raise AssertionError(f'Unexpected python version {sys.version}')
|
||||
|
||||
if sys.platform == 'win32':
|
||||
import py2exe # noqa: F401 # Yes, this *is* used
|
||||
dist_dir = 'dist.win32'
|
||||
|
||||
else:
|
||||
raise AssertionError(f'Unsupported platform {sys.platform}')
|
||||
|
||||
# This added to make mypy happy
|
||||
assert sys.platform == 'win32'
|
||||
###########################################################################
|
||||
|
||||
###########################################################################
|
||||
# Retrieve current git short hash and store in file GITVERSION_FILE
|
||||
###########################################################################
|
||||
git_shorthash = git_shorthash_from_head()
|
||||
if git_shorthash is None:
|
||||
exit(-1)
|
||||
|
||||
with open(GITVERSION_FILE, 'w+', encoding='utf-8') as gvf:
|
||||
gvf.write(git_shorthash)
|
||||
|
||||
print(f'Git short hash: {git_shorthash}')
|
||||
###########################################################################
|
||||
|
||||
###########################################################################
|
||||
# Misc. Configuration
|
||||
###########################################################################
|
||||
# Split version, as py2exe wants the 'base' for version
|
||||
semver = appversion()
|
||||
appversion_str = str(semver)
|
||||
base_appversion = str(semver.truncate('patch'))
|
||||
|
||||
# Ensure a clean `dist_dir` by first removing it.
|
||||
if dist_dir and len(dist_dir) > 1 and isdir(dist_dir):
|
||||
shutil.rmtree(dist_dir)
|
||||
|
||||
# Windows paths
|
||||
WIXPATH = r'C:\Program Files (x86)\WiX Toolset v3.11\bin'
|
||||
SDKPATH = r'C:\Program Files (x86)\Windows Kits\10\bin\10.0.19041.0\x86'
|
||||
###########################################################################
|
||||
|
||||
###########################################################################
|
||||
|
||||
###########################################################################
|
||||
# Set up all the options, extra files etc. for py2exe build.
|
||||
###########################################################################
|
||||
APP = 'EDMarketConnector.py'
|
||||
APPCMD = 'EDMC.py'
|
||||
PLUGINS = [
|
||||
'plugins/coriolis.py',
|
||||
'plugins/eddn.py',
|
||||
'plugins/edsm.py',
|
||||
'plugins/edsy.py',
|
||||
'plugins/inara.py',
|
||||
]
|
||||
|
||||
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
|
||||
'pkg_resources._vendor.platformdirs', # Necessary 2023-01-17
|
||||
'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),
|
||||
]
|
||||
###########################################################################
|
||||
|
||||
###########################################################################
|
||||
# Use py2exe's `freeze()` to produce the executables.
|
||||
###########################################################################
|
||||
freeze(
|
||||
version_info={
|
||||
'description': 'Downloads commodity market and other station data from the game Elite Dangerous for use with'
|
||||
' all popular online and offline trading tools.',
|
||||
'company_name': 'EDCD', # Used by WinSparkle
|
||||
'product_name': appname, # Used by WinSparkle
|
||||
'version': base_appversion,
|
||||
'product_version': appversion_str,
|
||||
'copyright': copyright,
|
||||
'language': 'English (United States)',
|
||||
},
|
||||
windows=[
|
||||
{
|
||||
'dest_base': appname,
|
||||
'script': APP,
|
||||
'icon_resources': [(0, f'{appname}.ico')],
|
||||
'other_resources': [(24, 1, open(f'{appname}.manifest').read())],
|
||||
}
|
||||
],
|
||||
console=[
|
||||
{
|
||||
'dest_base': appcmdname,
|
||||
'script': APPCMD,
|
||||
'other_resources': [(24, 1, open(f'{appcmdname}.manifest').read())],
|
||||
}
|
||||
],
|
||||
data_files=DATA_FILES,
|
||||
options=OPTIONS,
|
||||
)
|
||||
###########################################################################
|
||||
|
||||
###########################################################################
|
||||
# Build installer(s)
|
||||
###########################################################################
|
||||
package_filename = None
|
||||
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}')
|
||||
|
||||
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"')
|
||||
|
||||
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': 'RegistryEntries'
|
||||
},
|
||||
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
|
||||
)
|
||||
|
||||
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}')
|
||||
###########################################################################
|
@ -611,7 +611,7 @@ Developers
|
||||
* We now build using the new, `setuptools` mediated py2exe `freeze()` method,
|
||||
so we're in the clear for when `distutils` is removed in Python 3.12.
|
||||
* The old `setup.py` file, along with associated `py2exe.cmd` have been removed
|
||||
in favour of the new `Build-exe-and-msi.py` file. Documentation updated.
|
||||
in favour of the new `build.py` file. Documentation updated.
|
||||
|
||||
---
|
||||
|
||||
|
@ -389,7 +389,7 @@ information about this build process.
|
||||
|
||||
Thus, you **MUST** check if any imports you add in `plugins/*.py` files are only
|
||||
referenced in that file (or also only in any other core plugin), and if so
|
||||
**YOU MUST ENSURE THAT PERTINENT ADJUSTMENTS ARE MADE IN `Build-exe-and-msi.py`
|
||||
**YOU MUST ENSURE THAT PERTINENT ADJUSTMENTS ARE MADE IN `build.py`
|
||||
IN ORDER TO ENSURE THE FILES ARE ACTUALLY PRESENT IN AN END-USER
|
||||
INSTALLATION ON WINDOWS.**
|
||||
|
||||
@ -409,7 +409,7 @@ the appropriate `packages` definition to:
|
||||
Note that in this case it's in `packages` because we want the whole directory
|
||||
adding. For a single file an extra item in `includes` would suffice.
|
||||
|
||||
Such additions to `Build-exe-and-msi.py` should not cause any issues if
|
||||
Such additions to `build.py` should not cause any issues if
|
||||
subsequent project changes cause `py2exe` to automatically pick up the same
|
||||
file(s).
|
||||
|
||||
|
306
build.py
Normal file
306
build.py
Normal file
@ -0,0 +1,306 @@
|
||||
"""
|
||||
build.py - Build the Installer
|
||||
Copyright (c) EDCD, All Rights Reserved
|
||||
Licensed under the GNU General Public License.
|
||||
See LICENSE file.
|
||||
"""
|
||||
import os
|
||||
import re
|
||||
import shutil
|
||||
import subprocess
|
||||
import sys
|
||||
import pathlib
|
||||
from os.path import exists, join, isdir
|
||||
from tempfile import gettempdir
|
||||
from lxml import etree
|
||||
import py2exe
|
||||
from config import (
|
||||
appcmdname,
|
||||
appname,
|
||||
appversion,
|
||||
appversion_nobuild,
|
||||
copyright,
|
||||
git_shorthash_from_head,
|
||||
)
|
||||
|
||||
|
||||
def system_check(dist_dir):
|
||||
if sys.version_info < (3, 11):
|
||||
sys.exit(f"Unexpected Python version {sys.version}")
|
||||
|
||||
if sys.platform != "win32":
|
||||
sys.exit(f"Unsupported platform {sys.platform}")
|
||||
|
||||
git_shorthash = git_shorthash_from_head()
|
||||
if git_shorthash is None:
|
||||
sys.exit("Invalid Git Hash")
|
||||
|
||||
gitversion_file = ".gitversion"
|
||||
with open(gitversion_file, "w+", encoding="utf-8") as gvf:
|
||||
gvf.write(git_shorthash)
|
||||
|
||||
print(f"Git short hash: {git_shorthash}")
|
||||
|
||||
if dist_dir and len(dist_dir) > 1 and isdir(dist_dir):
|
||||
shutil.rmtree(dist_dir)
|
||||
return gitversion_file
|
||||
|
||||
|
||||
def generate_data_files(app_name, gitversion_file):
|
||||
l10n_dir = "L10n"
|
||||
fdevids_dir = "FDevIDs"
|
||||
data_files = [
|
||||
(
|
||||
"",
|
||||
[
|
||||
gitversion_file,
|
||||
"WinSparkle.dll",
|
||||
"WinSparkle.pdb",
|
||||
"EUROCAPS.TTF",
|
||||
"ChangeLog.md",
|
||||
"snd_good.wav",
|
||||
"snd_bad.wav",
|
||||
"modules.p",
|
||||
"ships.p",
|
||||
f"{app_name}.VisualElementsManifest.xml",
|
||||
f"{app_name}.ico",
|
||||
"EDMarketConnector - TRACE.bat",
|
||||
"EDMarketConnector - localserver-auth.bat",
|
||||
"EDMarketConnector - reset-ui.bat",
|
||||
],
|
||||
),
|
||||
(
|
||||
l10n_dir,
|
||||
[join(l10n_dir, x) for x in os.listdir(l10n_dir) if x.endswith(".strings")],
|
||||
),
|
||||
(
|
||||
fdevids_dir,
|
||||
[
|
||||
join(fdevids_dir, "commodity.csv"),
|
||||
join(fdevids_dir, "rare_commodity.csv"),
|
||||
],
|
||||
),
|
||||
("plugins", PLUGINS),
|
||||
]
|
||||
return data_files
|
||||
|
||||
|
||||
def windows_installer_display_lang(app_name, filename):
|
||||
lcids = [
|
||||
int(x)
|
||||
for x in re.search( # type: ignore
|
||||
r'Languages\s*=\s*"(.+?)"', open(f"{app_name}.wxs", encoding="UTF8").read()
|
||||
)
|
||||
.group(1)
|
||||
.split(",")
|
||||
]
|
||||
assert lcids[0] == 1033, f"Default language is {lcids[0]}, should be 1033 (en_US)"
|
||||
shutil.copyfile(filename, join(gettempdir(), f"{app_name}_1033.msi"))
|
||||
for lcid in lcids[1:]:
|
||||
shutil.copyfile(
|
||||
join(gettempdir(), f"{app_name}_1033.msi"),
|
||||
join(gettempdir(), f"{app_name}_{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()}\{app_name}_{lcid}.msi Product {lcid}'
|
||||
)
|
||||
os.system(
|
||||
rf'"{SDKPATH}\MsiTran.Exe" -g {gettempdir()}\{app_name}_1033.msi {gettempdir()}\{app_name}_{lcid}.msi {gettempdir()}\{lcid}.mst'
|
||||
) # noqa: E501 # Not going to get shorter
|
||||
os.system(
|
||||
rf'cscript /nologo "{SDKPATH}\WiSubStg.vbs" {filename} {gettempdir()}\{lcid}.mst {lcid}'
|
||||
)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
DIST_DIR = "dist.win32"
|
||||
GITVERSION_FILENAME = system_check(DIST_DIR)
|
||||
# Constants
|
||||
WIXPATH = rf"{os.environ['WIX']}\bin"
|
||||
SDKPATH = r"C:\Program Files (x86)\Windows Kits\10\bin\10.0.19041.0\x86"
|
||||
PLUGINS = [
|
||||
"plugins/coriolis.py",
|
||||
"plugins/eddn.py",
|
||||
"plugins/edsm.py",
|
||||
"plugins/edsy.py",
|
||||
"plugins/inara.py",
|
||||
]
|
||||
OPTIONS = {
|
||||
"py2exe": {
|
||||
"dist_dir": DIST_DIR,
|
||||
"optimize": 2,
|
||||
"packages": [
|
||||
"asyncio",
|
||||
"multiprocessing",
|
||||
"pkg_resources._vendor.platformdirs",
|
||||
"sqlite3",
|
||||
"util",
|
||||
],
|
||||
"includes": ["dataclasses", "shutil", "timeout_session", "zipfile"],
|
||||
"excludes": [
|
||||
"distutils",
|
||||
"_markerlib",
|
||||
"optparse",
|
||||
"PIL",
|
||||
"simplejson",
|
||||
"unittest",
|
||||
"doctest",
|
||||
"pdb",
|
||||
"difflib",
|
||||
],
|
||||
}
|
||||
}
|
||||
|
||||
# Function to generate DATA_FILES list
|
||||
DATA_FILES = generate_data_files(appname, GITVERSION_FILENAME)
|
||||
|
||||
version_info = {
|
||||
"description": "Downloads commodity market and other station data from the game"
|
||||
" Elite Dangerous for use with all popular online and offline trading tools.",
|
||||
"company_name": "EDCD", # Used by WinSparkle
|
||||
"product_name": appname, # Used by WinSparkle
|
||||
"version": str(appversion().truncate()),
|
||||
"product_version": str(appversion()),
|
||||
"copyright": copyright,
|
||||
"language": "English (United States)",
|
||||
}
|
||||
|
||||
windows_config = {
|
||||
"dest_base": appname,
|
||||
"script": "EDMarketConnector.py",
|
||||
"icon_resources": [(0, f"{appname}.ico")],
|
||||
"other_resources": [
|
||||
(24, 1, pathlib.Path(f"{appname}.manifest").read_text(encoding="UTF8"))
|
||||
],
|
||||
}
|
||||
|
||||
console_config = {
|
||||
"dest_base": appcmdname,
|
||||
"script": "EDMC.py",
|
||||
"other_resources": [
|
||||
(24, 1, pathlib.Path(f"{appcmdname}.manifest").read_text(encoding="UTF8"))
|
||||
],
|
||||
}
|
||||
|
||||
py2exe.freeze(
|
||||
version_info=version_info,
|
||||
windows=[windows_config],
|
||||
console=[console_config],
|
||||
data_files=DATA_FILES,
|
||||
options=OPTIONS,
|
||||
)
|
||||
|
||||
###########################################################################
|
||||
# Build installer(s)
|
||||
###########################################################################
|
||||
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
|
||||
heat_command = [
|
||||
str(join(WIXPATH, "heat.exe")),
|
||||
"dir",
|
||||
str(DIST_DIR),
|
||||
"-ag",
|
||||
"-sfrag",
|
||||
"-srid",
|
||||
"-suid",
|
||||
"-out",
|
||||
str(components_file),
|
||||
]
|
||||
subprocess.run(heat_command, check=True)
|
||||
|
||||
component_tree = etree.parse(str(components_file))
|
||||
# Modify component_tree as described in the original code...
|
||||
|
||||
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)")
|
||||
|
||||
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,
|
||||
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": "RegistryEntries"},
|
||||
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
|
||||
)
|
||||
|
||||
candle_command = rf'"{WIXPATH}\candle.exe" {appname}.wxs'
|
||||
subprocess.run(candle_command, shell=True, check=True)
|
||||
|
||||
if not exists(f"{appname}.wixobj"):
|
||||
raise AssertionError(f"No {appname}.wixobj: candle.exe failed?")
|
||||
|
||||
package_filename = f"{appname}_win_{appversion_nobuild()}.msi"
|
||||
light_command = rf'"{WIXPATH}\light.exe" -b {DIST_DIR}\ -sacl -spdb -sw1076 {appname}.wixobj -out {package_filename}'
|
||||
subprocess.run(light_command, shell=True, check=True)
|
||||
|
||||
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
|
||||
windows_installer_display_lang(appname, package_filename)
|
||||
###########################################################################
|
@ -52,7 +52,7 @@ appcmdname = 'EDMC'
|
||||
# <https://semver.org/#semantic-versioning-specification-semver>
|
||||
# Major.Minor.Patch(-prerelease)(+buildmetadata)
|
||||
# NB: Do *not* import this, use the functions appversion() and appversion_nobuild()
|
||||
_static_appversion = '5.9.0'
|
||||
_static_appversion = '5.9.1-alpha0'
|
||||
_cached_version: Optional[semantic_version.Version] = None
|
||||
copyright = '© 2015-2019 Jonathan Harris, 2020-2023 EDCD'
|
||||
|
||||
|
@ -61,7 +61,7 @@ You will need several pieces of software installed, or the files from their
|
||||
|
||||
If you are using different versions of any of these tools then please ensure
|
||||
that the paths where they're installed match the associated lines in
|
||||
`Build-exe-and-msi.py`. i.e. if you're using later WiX you might need to edit
|
||||
`build.py`. i.e. if you're using later WiX you might need to edit
|
||||
the WIXPATH line, and likewise the SDKPATH line if you're using a later
|
||||
Windows SDK kit.
|
||||
|
||||
@ -100,13 +100,13 @@ resulting .exe and/or .msi files. **But** realise that the resulting program
|
||||
will still try to check for new versions at the main URL unless you change
|
||||
that.
|
||||
|
||||
1. Company is set in `Build-exe-and-msi.py`. Search for `company_name`. This
|
||||
1. Company is set in `build.py`. Search for `company_name`. This
|
||||
is what appears in the EXE properties, and is also used as the location of
|
||||
WinSparkle registry entries on Windows.
|
||||
|
||||
1. Application names, version and URL of the file with latest release
|
||||
information. These are all in the `config/__init__.py` file. See the
|
||||
`from config import ...` lines in `Build-exe-and-msi.py`:
|
||||
`from config import ...` lines in `build.py`:
|
||||
1. `appname`: The short appname, e.g. 'EDMarketConnector'
|
||||
2. `applongname`: The long appname, e.g. 'E:D Market Connector'
|
||||
3. `appcmdname`: The CLI appname, e.g. 'EDMC'
|
||||
@ -144,9 +144,9 @@ that.
|
||||
If you add a new file to the program that needs to be distributed to users as
|
||||
well then you will need to properly add it to the build process.
|
||||
|
||||
### Build-exe-and-msi.py
|
||||
### build.py
|
||||
|
||||
You'll need to add it in `Build-exe-and-msi.py` so that py2exe includes it in
|
||||
You'll need to add it in `build.py` so that py2exe includes it in
|
||||
the build. Add the file to the DATA_FILES statement.
|
||||
|
||||
### WiX
|
||||
@ -260,19 +260,19 @@ a 'Git bash' window. The 'Terminal' tab of PyCharm works fine.
|
||||
Assuming the correct python.exe is associated with .py files then simply run:
|
||||
|
||||
```batch
|
||||
Build-exe-and-msi.py
|
||||
build.py
|
||||
```
|
||||
|
||||
else you might need this, which assumes correct python.exe is in your PATH:
|
||||
|
||||
```batch
|
||||
python.exe Build-exe-and-msi.py
|
||||
python.exe build.py
|
||||
```
|
||||
|
||||
else you'll have to specify the path to python.exe, e.g.:
|
||||
|
||||
```batch
|
||||
"C:\Program Files \(x86)\Python38-32\python.exe" Build-exe-and-msi.py
|
||||
"C:\Program Files \(x86)\Python38-32\python.exe" build.py
|
||||
```
|
||||
|
||||
Output will be something like (`...` denoting parts elided for brevity):
|
||||
@ -308,7 +308,7 @@ Done
|
||||
|
||||
**Do check the output** for things like not properly specifying extra files
|
||||
to be included in the install. If they're not picked up by current rules in
|
||||
`Build-exe-and-msi.py` then you will need to add them to the `win32`
|
||||
`build.py` then you will need to add them to the `win32`
|
||||
`DATA_FILES` array.
|
||||
|
||||
You should now have one new/updated folder `dist.win32` and two new files
|
||||
@ -456,6 +456,6 @@ When changing the Python version (Major.Minor.Patch) used:
|
||||
|
||||
1. Major or Minor level changes:
|
||||
|
||||
1. `Build-exe-and-msi.py` will need its version check updating.
|
||||
1. `build.py` will need its version check updating.
|
||||
2. `.pre-commit-config.yaml` will need the `default_language_version`
|
||||
section updated to the appropriate version.
|
||||
|
@ -6,7 +6,7 @@ this is to include an XML-format .manifest file at build time.
|
||||
|
||||
## Build time
|
||||
|
||||
We specify .manifest files in `Build-exe-and-msi.py`.
|
||||
We specify .manifest files in `build.py`.
|
||||
|
||||
## Editing or changing a manifest
|
||||
|
||||
|
@ -16,7 +16,7 @@
|
||||
# referenced in this file (or only in any other core plugin), and if so...
|
||||
#
|
||||
# YOU MUST ENSURE THAT PERTINENT ADJUSTMENTS ARE MADE IN
|
||||
# `Build-exe-and-msi.py` SO AS TO ENSURE THE FILES ARE ACTUALLY PRESENT
|
||||
# `build.py` SO AS TO ENSURE THE FILES ARE ACTUALLY PRESENT
|
||||
# IN AN END-USER INSTALLATION ON WINDOWS.
|
||||
#
|
||||
# ! $# ! $# ! $# ! $# ! $# ! $# ! $# ! $# ! $# ! $# ! $# ! $# ! $# ! $# ! $#
|
||||
|
@ -16,7 +16,7 @@
|
||||
# referenced in this file (or only in any other core plugin), and if so...
|
||||
#
|
||||
# YOU MUST ENSURE THAT PERTINENT ADJUSTMENTS ARE MADE IN
|
||||
# `Build-exe-and-msi.py` SO AS TO ENSURE THE FILES ARE ACTUALLY PRESENT
|
||||
# `build.py` SO AS TO ENSURE THE FILES ARE ACTUALLY PRESENT
|
||||
# IN AN END-USER INSTALLATION ON WINDOWS.
|
||||
#
|
||||
# ! $# ! $# ! $# ! $# ! $# ! $# ! $# ! $# ! $# ! $# ! $# ! $# ! $# ! $# ! $#
|
||||
|
@ -25,7 +25,7 @@
|
||||
# referenced in this file (or only in any other core plugin), and if so...
|
||||
#
|
||||
# YOU MUST ENSURE THAT PERTINENT ADJUSTMENTS ARE MADE IN
|
||||
# `Build-exe-and-msi.py` SO AS TO ENSURE THE FILES ARE ACTUALLY PRESENT IN
|
||||
# `build.py` SO AS TO ENSURE THE FILES ARE ACTUALLY PRESENT IN
|
||||
# AN END-USER INSTALLATION ON WINDOWS.
|
||||
#
|
||||
#
|
||||
|
@ -16,7 +16,7 @@
|
||||
# referenced in this file (or only in any other core plugin), and if so...
|
||||
#
|
||||
# YOU MUST ENSURE THAT PERTINENT ADJUSTMENTS ARE MADE IN
|
||||
# `Build-exe-and-msi.py` SO AS TO ENSURE THE FILES ARE ACTUALLY PRESENT IN
|
||||
# `build.py` SO AS TO ENSURE THE FILES ARE ACTUALLY PRESENT IN
|
||||
# AN END-USER INSTALLATION ON WINDOWS.
|
||||
#
|
||||
#
|
||||
|
@ -16,7 +16,7 @@
|
||||
# referenced in this file (or only in any other core plugin), and if so...
|
||||
#
|
||||
# YOU MUST ENSURE THAT PERTINENT ADJUSTMENTS ARE MADE IN
|
||||
# `Build-exe-and-msi.py` SO AS TO ENSURE THE FILES ARE ACTUALLY PRESENT
|
||||
# `build.py` SO AS TO ENSURE THE FILES ARE ACTUALLY PRESENT
|
||||
# IN AN END-USER INSTALLATION ON WINDOWS.
|
||||
#
|
||||
# ! $# ! $# ! $# ! $# ! $# ! $# ! $# ! $# ! $# ! $# ! $# ! $# ! $# ! $# ! $#
|
||||
|
Loading…
x
Reference in New Issue
Block a user