43bd0ec8e6
- Erstelle generate_wix_files.py zum Ersetzen von 'wix heat' - Migriere DocuMentor.wxs auf WiX v4/v6-Syntax - Füge build_msi.py für automatisierten Build hinzu - Aktualisiere Dokumentation für WiX v6 - Erweitere .gitignore für WiX-Artefakte WiX v6 hat das 'heat' Tool entfernt, daher wurde ein Python-Skript erstellt, das automatisch alle Dateien aus dist/DocuMentor harvested und eine WiX-konforme ProductFiles.wxs generiert. Der neue Build-Prozess: 1. uv run python build_windows.py 2. uv run python generate_wix_files.py 3. wix build DocuMentor.wxs ProductFiles.wxs -o DocuMentor.msi Oder vereinfacht: uv run python build_msi.py
185 lines
5.6 KiB
Python
185 lines
5.6 KiB
Python
"""
|
|
WiX ProductFiles.wxs Generator
|
|
|
|
Generiert automatisch eine WXS-Datei mit allen Dateien aus dist/DocuMentor.
|
|
Ersetzt die veraltete 'wix heat' Funktionalität für WiX v6.
|
|
"""
|
|
|
|
import uuid
|
|
from pathlib import Path
|
|
from xml.etree import ElementTree as ET
|
|
|
|
|
|
def generate_guid() -> str:
|
|
"""Generiert eine neue GUID."""
|
|
return str(uuid.uuid4()).upper()
|
|
|
|
|
|
def sanitize_id(name: str) -> str:
|
|
"""
|
|
Macht einen String WiX-konform für IDs.
|
|
|
|
WiX erlaubt nur: A-Z, a-z, 0-9, _, .
|
|
Darf nicht mit Zahl beginnen.
|
|
"""
|
|
# Ersetze illegale Zeichen
|
|
sanitized = name.replace("-", "_").replace(" ", "_").replace(".", "_")
|
|
|
|
# Entferne alle anderen nicht erlaubten Zeichen
|
|
allowed = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789_"
|
|
sanitized = "".join(c if c in allowed else "_" for c in sanitized)
|
|
|
|
# Darf nicht mit Zahl beginnen
|
|
if sanitized and sanitized[0].isdigit():
|
|
sanitized = f"_{sanitized}"
|
|
|
|
return sanitized
|
|
|
|
|
|
def create_wix_fragment(dist_dir: Path) -> ET.Element:
|
|
"""
|
|
Erstellt ein WiX Fragment mit allen Dateien aus dem dist Verzeichnis.
|
|
|
|
Args:
|
|
dist_dir: Pfad zum dist/DocuMentor Verzeichnis
|
|
|
|
Returns:
|
|
ET.Element: Wix Root-Element mit Fragment
|
|
"""
|
|
# Namespace (WiX v4+)
|
|
ns = "http://wixtoolset.org/schemas/v4/wxs"
|
|
ET.register_namespace("", ns)
|
|
|
|
# Root Element
|
|
wix = ET.Element(f"{{{ns}}}Wix")
|
|
fragment = ET.SubElement(wix, f"{{{ns}}}Fragment")
|
|
component_group = ET.SubElement(
|
|
fragment, f"{{{ns}}}ComponentGroup", {"Id": "ProductComponents", "Directory": "INSTALLFOLDER"}
|
|
)
|
|
|
|
# Sammle alle Dateien
|
|
all_files = sorted(dist_dir.rglob("*"))
|
|
files_only = [f for f in all_files if f.is_file()]
|
|
|
|
print(f"Gefunden: {len(files_only)} Dateien")
|
|
|
|
# Gruppiere nach Verzeichnis
|
|
dirs_dict: dict[Path, list[Path]] = {}
|
|
for file in files_only:
|
|
rel_dir = file.parent.relative_to(dist_dir)
|
|
if rel_dir not in dirs_dict:
|
|
dirs_dict[rel_dir] = []
|
|
dirs_dict[rel_dir].append(file)
|
|
|
|
# Erstelle Directory Fragments
|
|
dir_fragment = ET.SubElement(wix, f"{{{ns}}}Fragment")
|
|
|
|
# INSTALLFOLDER ist bereits in DocuMentor.wxs definiert
|
|
directory_ref = ET.SubElement(dir_fragment, f"{{{ns}}}DirectoryRef", {"Id": "INSTALLFOLDER"})
|
|
|
|
# Erstelle Verzeichnisstruktur
|
|
created_dirs = {"INSTALLFOLDER": directory_ref}
|
|
|
|
for rel_dir in sorted(dirs_dict.keys()):
|
|
if rel_dir == Path("."):
|
|
continue
|
|
|
|
parts = rel_dir.parts
|
|
parent_id = "INSTALLFOLDER"
|
|
|
|
for i, part in enumerate(parts):
|
|
current_path = Path(*parts[: i + 1])
|
|
dir_id = f"Dir_{sanitize_id(current_path.as_posix().replace('/', '_'))}"
|
|
|
|
if dir_id not in created_dirs:
|
|
parent_elem = created_dirs[parent_id]
|
|
new_dir = ET.SubElement(parent_elem, f"{{{ns}}}Directory", {"Id": dir_id, "Name": part})
|
|
created_dirs[dir_id] = new_dir
|
|
parent_id = dir_id
|
|
else:
|
|
parent_id = dir_id
|
|
|
|
# Füge Komponenten hinzu
|
|
component_counter = 0
|
|
|
|
for rel_dir, files in sorted(dirs_dict.items()):
|
|
if rel_dir == Path("."):
|
|
dir_id = "INSTALLFOLDER"
|
|
else:
|
|
dir_id = f"Dir_{sanitize_id(rel_dir.as_posix().replace('/', '_'))}"
|
|
|
|
# Erstelle eine Komponente pro Verzeichnis
|
|
component_id = f"Component_{sanitize_id(dir_id)}"
|
|
component_counter += 1
|
|
|
|
component = ET.SubElement(
|
|
component_group, f"{{{ns}}}Component", {"Id": component_id, "Directory": dir_id, "Guid": generate_guid()}
|
|
)
|
|
|
|
# Füge alle Dateien des Verzeichnisses hinzu
|
|
for idx, file in enumerate(files):
|
|
file_id = f"File_{sanitize_id(component_id)}_{idx}"
|
|
# Absoluter Pfad für WiX
|
|
source_path = str(file).replace("/", "\\")
|
|
|
|
file_attribs = {"Id": file_id, "Source": source_path, "Name": file.name}
|
|
|
|
# Erste Datei ist KeyPath
|
|
if idx == 0:
|
|
file_attribs["KeyPath"] = "yes"
|
|
|
|
ET.SubElement(component, f"{{{ns}}}File", file_attribs)
|
|
|
|
print(f"Erstellt: {component_counter} Komponenten")
|
|
|
|
return wix
|
|
|
|
|
|
def format_xml(element: ET.Element, level: int = 0) -> None:
|
|
"""Formatiert XML mit Einrückungen (in-place)."""
|
|
indent = " "
|
|
i = f"\n{indent * level}"
|
|
|
|
if len(element):
|
|
if not element.text or not element.text.strip():
|
|
element.text = i + indent
|
|
if not element.tail or not element.tail.strip():
|
|
element.tail = i
|
|
last_child = None
|
|
for child in element:
|
|
format_xml(child, level + 1)
|
|
last_child = child
|
|
if last_child is not None and (not last_child.tail or not last_child.tail.strip()):
|
|
last_child.tail = i
|
|
else:
|
|
if level and (not element.tail or not element.tail.strip()):
|
|
element.tail = i
|
|
|
|
|
|
def main():
|
|
"""Hauptfunktion."""
|
|
dist_dir = Path("dist/DocuMentor")
|
|
output_file = Path("ProductFiles.wxs")
|
|
|
|
if not dist_dir.exists():
|
|
print(f"FEHLER: {dist_dir} existiert nicht!")
|
|
print("Führe zuerst 'uv run pyinstaller DocuMentor.spec' aus.")
|
|
return
|
|
|
|
print(f"Generiere ProductFiles.wxs aus {dist_dir}...")
|
|
|
|
wix_root = create_wix_fragment(dist_dir)
|
|
format_xml(wix_root)
|
|
|
|
# Schreibe XML
|
|
tree = ET.ElementTree(wix_root)
|
|
tree.write(output_file, encoding="utf-8", xml_declaration=True)
|
|
|
|
print(f"[OK] {output_file} erfolgreich erstellt!")
|
|
print(f"\nNaechste Schritte:")
|
|
print(f"1. wix build DocuMentor.wxs ProductFiles.wxs -o DocuMentor.msi")
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|