Füge WiX v6-kompatiblen MSI-Installer hinzu
- 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
This commit is contained in:
@@ -17,3 +17,9 @@ version_info.txt
|
|||||||
|
|
||||||
# Virtual environments
|
# Virtual environments
|
||||||
.venv
|
.venv
|
||||||
|
|
||||||
|
# WiX Installer Build-Artefakte
|
||||||
|
ProductFiles.wxs
|
||||||
|
*.msi
|
||||||
|
*.wixpdb
|
||||||
|
.wix/
|
||||||
|
|||||||
@@ -0,0 +1,86 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<Wix xmlns="http://wixtoolset.org/schemas/v4/wxs">
|
||||||
|
|
||||||
|
<!-- Paket-Definition (ersetzt Product in v4) -->
|
||||||
|
<Package
|
||||||
|
Name="DocuMentor"
|
||||||
|
Version="0.1.0"
|
||||||
|
Manufacturer="Ihr Name/Firma"
|
||||||
|
UpgradeCode="F498B66C-726D-44AA-95F4-CB4FBDCEF26E"
|
||||||
|
Language="1031"
|
||||||
|
Compressed="yes"
|
||||||
|
InstallerVersion="500">
|
||||||
|
|
||||||
|
<MajorUpgrade
|
||||||
|
DowngradeErrorMessage="Eine neuere Version ist bereits installiert."
|
||||||
|
AllowSameVersionUpgrades="yes" />
|
||||||
|
|
||||||
|
<!-- Media Template -->
|
||||||
|
<MediaTemplate EmbedCab="yes" />
|
||||||
|
|
||||||
|
<!-- Feature-Definition -->
|
||||||
|
<Feature Id="ProductFeature" Title="DocuMentor" Level="1">
|
||||||
|
<ComponentGroupRef Id="ProductComponents" />
|
||||||
|
<ComponentRef Id="ApplicationShortcut" />
|
||||||
|
<ComponentRef Id="DesktopShortcut" />
|
||||||
|
</Feature>
|
||||||
|
|
||||||
|
<!-- Minimal UI (Standard Windows Installer Dialog) -->
|
||||||
|
|
||||||
|
<!-- Icon -->
|
||||||
|
<Icon Id="icon.ico" SourceFile="resources\icon.ico"/>
|
||||||
|
<Property Id="ARPPRODUCTICON" Value="icon.ico" />
|
||||||
|
<Property Id="ARPHELPLINK" Value="https://github.com/IhrRepo/DocuMentor" />
|
||||||
|
</Package>
|
||||||
|
|
||||||
|
<!-- Fragment: Verzeichnisstruktur -->
|
||||||
|
<Fragment>
|
||||||
|
<StandardDirectory Id="ProgramFilesFolder">
|
||||||
|
<Directory Id="INSTALLFOLDER" Name="DocuMentor" />
|
||||||
|
</StandardDirectory>
|
||||||
|
|
||||||
|
<StandardDirectory Id="ProgramMenuFolder">
|
||||||
|
<Directory Id="ApplicationProgramsFolder" Name="DocuMentor"/>
|
||||||
|
</StandardDirectory>
|
||||||
|
|
||||||
|
<StandardDirectory Id="DesktopFolder" />
|
||||||
|
</Fragment>
|
||||||
|
|
||||||
|
<!-- Fragment: Shortcuts -->
|
||||||
|
<Fragment>
|
||||||
|
<Component Id="ApplicationShortcut" Directory="ApplicationProgramsFolder" Guid="A498B66C-726D-44AA-95F4-CB4FBDCEF26E">
|
||||||
|
<Shortcut
|
||||||
|
Id="ApplicationStartMenuShortcut"
|
||||||
|
Name="DocuMentor"
|
||||||
|
Description="XSL-Transformations-Verwaltung"
|
||||||
|
Target="[INSTALLFOLDER]DocuMentor.exe"
|
||||||
|
WorkingDirectory="INSTALLFOLDER"
|
||||||
|
Icon="icon.ico" />
|
||||||
|
<RemoveFolder Id="CleanUpShortCut" On="uninstall"/>
|
||||||
|
<RegistryValue
|
||||||
|
Root="HKCU"
|
||||||
|
Key="Software\DocuMentor"
|
||||||
|
Name="installed"
|
||||||
|
Type="integer"
|
||||||
|
Value="1"
|
||||||
|
KeyPath="yes"/>
|
||||||
|
</Component>
|
||||||
|
|
||||||
|
<Component Id="DesktopShortcut" Directory="DesktopFolder" Guid="B498B66C-726D-44AA-95F4-CB4FBDCEF26E">
|
||||||
|
<Shortcut
|
||||||
|
Id="DesktopShortcutId"
|
||||||
|
Name="DocuMentor"
|
||||||
|
Target="[INSTALLFOLDER]DocuMentor.exe"
|
||||||
|
WorkingDirectory="INSTALLFOLDER"
|
||||||
|
Icon="icon.ico" />
|
||||||
|
<RegistryValue
|
||||||
|
Root="HKCU"
|
||||||
|
Key="Software\DocuMentor"
|
||||||
|
Name="desktopShortcut"
|
||||||
|
Type="integer"
|
||||||
|
Value="1"
|
||||||
|
KeyPath="yes"/>
|
||||||
|
</Component>
|
||||||
|
</Fragment>
|
||||||
|
|
||||||
|
</Wix>
|
||||||
@@ -0,0 +1,53 @@
|
|||||||
|
"""
|
||||||
|
WiX MSI Build-Skript für DocuMentor (WiX v6)
|
||||||
|
|
||||||
|
Erstellt einen MSI-Installer aus dem PyInstaller Build.
|
||||||
|
"""
|
||||||
|
|
||||||
|
import subprocess
|
||||||
|
import sys
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
|
||||||
|
def build_msi():
|
||||||
|
"""Erstellt MSI-Installer mit WiX v6."""
|
||||||
|
project_root = Path(__file__).parent
|
||||||
|
dist_dir = project_root / "dist" / "DocuMentor"
|
||||||
|
|
||||||
|
if not dist_dir.exists():
|
||||||
|
print("FEHLER: PyInstaller Build nicht gefunden!")
|
||||||
|
print("Bitte zuerst ausführen: uv run python build_windows.py")
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
print(f"DocuMentor Build gefunden: {dist_dir}")
|
||||||
|
print()
|
||||||
|
|
||||||
|
# Schritt 1: ProductFiles.wxs generieren (ersetzt WiX heat)
|
||||||
|
print("Schritt 1/2: Generiere ProductFiles.wxs...")
|
||||||
|
result = subprocess.run(["uv", "run", "python", "generate_wix_files.py"], check=False)
|
||||||
|
|
||||||
|
if result.returncode != 0:
|
||||||
|
print("\nFEHLER: ProductFiles.wxs Generierung fehlgeschlagen!")
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
print()
|
||||||
|
|
||||||
|
# Schritt 2: MSI kompilieren mit WiX v6
|
||||||
|
print("Schritt 2/2: Kompiliere MSI-Installer...")
|
||||||
|
result = subprocess.run(["wix", "build", "DocuMentor.wxs", "ProductFiles.wxs", "-o", "DocuMentor.msi"], check=False)
|
||||||
|
|
||||||
|
if result.returncode != 0:
|
||||||
|
print("\nFEHLER: MSI-Kompilierung fehlgeschlagen!")
|
||||||
|
print("Stelle sicher, dass WiX v6 installiert ist:")
|
||||||
|
print(" dotnet tool install --global wix")
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
print()
|
||||||
|
print("[OK] MSI erfolgreich erstellt: DocuMentor.msi")
|
||||||
|
print()
|
||||||
|
print("Installation testen mit:")
|
||||||
|
print(" msiexec /i DocuMentor.msi")
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
build_msi()
|
||||||
+24
-172
@@ -51,10 +51,8 @@ Dies erstellt:
|
|||||||
|
|
||||||
**Voraussetzungen:**
|
**Voraussetzungen:**
|
||||||
```bash
|
```bash
|
||||||
# WiX Toolset v4 installieren (empfohlen)
|
# WiX Toolset v6 installieren (aktuell)
|
||||||
dotnet tool install --global wix
|
dotnet tool install --global wix
|
||||||
|
|
||||||
# Oder WiX v3 von https://wixtoolset.org/releases/
|
|
||||||
```
|
```
|
||||||
|
|
||||||
**Schritt 1: PyInstaller Build erstellen**
|
**Schritt 1: PyInstaller Build erstellen**
|
||||||
@@ -62,166 +60,33 @@ dotnet tool install --global wix
|
|||||||
uv run python build_windows.py
|
uv run python build_windows.py
|
||||||
```
|
```
|
||||||
|
|
||||||
**Schritt 2: WiX-Konfiguration erstellen (`DocuMentor.wxs`):**
|
**Schritt 2: ProductFiles.wxs generieren**
|
||||||
```xml
|
|
||||||
<?xml version="1.0" encoding="UTF-8"?>
|
|
||||||
<Wix xmlns="http://schemas.microsoft.com/wix/2006/wi">
|
|
||||||
|
|
||||||
<!-- Produkt-Definition -->
|
**WICHTIG**: WiX v6 hat das `heat` Tool entfernt. Stattdessen verwenden wir ein Python-Skript:
|
||||||
<Product
|
|
||||||
Id="*"
|
|
||||||
Name="DocuMentor"
|
|
||||||
Language="1031"
|
|
||||||
Version="0.1.0"
|
|
||||||
Manufacturer="Ihr Name/Firma"
|
|
||||||
UpgradeCode="PUT-YOUR-UPGRADE-GUID-HERE">
|
|
||||||
|
|
||||||
<Package
|
|
||||||
InstallerVersion="200"
|
|
||||||
Compressed="yes"
|
|
||||||
InstallScope="perMachine"
|
|
||||||
Description="Professionelle XSL-Transformations-Verwaltung"
|
|
||||||
Comments="DocuMentor - XSL/PDF Management Tool" />
|
|
||||||
|
|
||||||
<!-- Upgrade-Logik (deinstalliert alte Versionen) -->
|
|
||||||
<MajorUpgrade
|
|
||||||
DowngradeErrorMessage="Eine neuere Version ist bereits installiert."
|
|
||||||
AllowSameVersionUpgrades="yes" />
|
|
||||||
|
|
||||||
<MediaTemplate EmbedCab="yes" />
|
|
||||||
|
|
||||||
<!-- Feature-Definition -->
|
|
||||||
<Feature Id="ProductFeature" Title="DocuMentor" Level="1">
|
|
||||||
<ComponentGroupRef Id="ProductComponents" />
|
|
||||||
<ComponentRef Id="ApplicationShortcut" />
|
|
||||||
<ComponentRef Id="DesktopShortcut" />
|
|
||||||
</Feature>
|
|
||||||
|
|
||||||
<!-- UI-Dialoge (Standard-Installer-Oberfläche) -->
|
|
||||||
<UIRef Id="WixUI_InstallDir" />
|
|
||||||
<Property Id="WIXUI_INSTALLDIR" Value="INSTALLFOLDER" />
|
|
||||||
|
|
||||||
<!-- Icon für Programme und Funktionen -->
|
|
||||||
<Icon Id="icon.ico" SourceFile="resources\icon.ico"/>
|
|
||||||
<Property Id="ARPPRODUCTICON" Value="icon.ico" />
|
|
||||||
|
|
||||||
<!-- Website-Link in Systemsteuerung -->
|
|
||||||
<Property Id="ARPHELPLINK" Value="https://github.com/IhrRepo/DocuMentor" />
|
|
||||||
</Product>
|
|
||||||
|
|
||||||
<!-- Fragment: Verzeichnisstruktur -->
|
|
||||||
<Fragment>
|
|
||||||
<Directory Id="TARGETDIR" Name="SourceDir">
|
|
||||||
<Directory Id="ProgramFilesFolder">
|
|
||||||
<Directory Id="INSTALLFOLDER" Name="DocuMentor" />
|
|
||||||
</Directory>
|
|
||||||
|
|
||||||
<!-- Start-Menü -->
|
|
||||||
<Directory Id="ProgramMenuFolder">
|
|
||||||
<Directory Id="ApplicationProgramsFolder" Name="DocuMentor"/>
|
|
||||||
</Directory>
|
|
||||||
|
|
||||||
<!-- Desktop -->
|
|
||||||
<Directory Id="DesktopFolder" Name="Desktop" />
|
|
||||||
</Directory>
|
|
||||||
</Fragment>
|
|
||||||
|
|
||||||
<!-- Fragment: Komponenten (Dateien) -->
|
|
||||||
<Fragment>
|
|
||||||
<ComponentGroup Id="ProductComponents" Directory="INSTALLFOLDER">
|
|
||||||
<!-- Hauptexecutable -->
|
|
||||||
<Component Id="MainExecutable" Guid="PUT-COMPONENT-GUID-HERE">
|
|
||||||
<File Id="DocuMentorEXE" Source="dist\DocuMentor\DocuMentor.exe" KeyPath="yes" />
|
|
||||||
</Component>
|
|
||||||
|
|
||||||
<!-- Alle weiteren Dateien aus dist/DocuMentor/ -->
|
|
||||||
<!-- HINWEIS: In Produktion heat.exe verwenden für automatisches Harvesting -->
|
|
||||||
<Component Id="AllFiles" Guid="PUT-COMPONENT-GUID-HERE-2">
|
|
||||||
<File Id="AllFilesMarker" Source="dist\DocuMentor\*" KeyPath="yes" />
|
|
||||||
</Component>
|
|
||||||
</ComponentGroup>
|
|
||||||
|
|
||||||
<!-- Start-Menü Verknüpfung -->
|
|
||||||
<DirectoryRef Id="ApplicationProgramsFolder">
|
|
||||||
<Component Id="ApplicationShortcut" Guid="PUT-COMPONENT-GUID-HERE-3">
|
|
||||||
<Shortcut
|
|
||||||
Id="ApplicationStartMenuShortcut"
|
|
||||||
Name="DocuMentor"
|
|
||||||
Description="XSL-Transformations-Verwaltung"
|
|
||||||
Target="[INSTALLFOLDER]DocuMentor.exe"
|
|
||||||
WorkingDirectory="INSTALLFOLDER"
|
|
||||||
Icon="icon.ico" />
|
|
||||||
<RemoveFolder Id="CleanUpShortCut" Directory="ApplicationProgramsFolder" On="uninstall"/>
|
|
||||||
<RegistryValue
|
|
||||||
Root="HKCU"
|
|
||||||
Key="Software\DocuMentor"
|
|
||||||
Name="installed"
|
|
||||||
Type="integer"
|
|
||||||
Value="1"
|
|
||||||
KeyPath="yes"/>
|
|
||||||
</Component>
|
|
||||||
</DirectoryRef>
|
|
||||||
|
|
||||||
<!-- Desktop Verknüpfung (optional) -->
|
|
||||||
<DirectoryRef Id="DesktopFolder">
|
|
||||||
<Component Id="DesktopShortcut" Guid="PUT-COMPONENT-GUID-HERE-4">
|
|
||||||
<Shortcut
|
|
||||||
Id="DesktopShortcut"
|
|
||||||
Name="DocuMentor"
|
|
||||||
Target="[INSTALLFOLDER]DocuMentor.exe"
|
|
||||||
WorkingDirectory="INSTALLFOLDER"
|
|
||||||
Icon="icon.ico" />
|
|
||||||
<RegistryValue
|
|
||||||
Root="HKCU"
|
|
||||||
Key="Software\DocuMentor"
|
|
||||||
Name="desktopShortcut"
|
|
||||||
Type="integer"
|
|
||||||
Value="1"
|
|
||||||
KeyPath="yes"/>
|
|
||||||
</Component>
|
|
||||||
</DirectoryRef>
|
|
||||||
</Fragment>
|
|
||||||
</Wix>
|
|
||||||
```
|
|
||||||
|
|
||||||
**Schritt 3: GUIDs generieren**
|
|
||||||
```bash
|
```bash
|
||||||
# GUIDs mit PowerShell generieren
|
# Generiert automatisch ProductFiles.wxs mit allen Dateien aus dist/DocuMentor
|
||||||
powershell -Command "[guid]::NewGuid()"
|
uv run python generate_wix_files.py
|
||||||
|
|
||||||
# Oder Python-Skript verwenden
|
|
||||||
uv run python generate_guid.py
|
|
||||||
```
|
```
|
||||||
|
|
||||||
**Schritt 4: Dateien automatisch harvesten (empfohlen)**
|
**Schritt 3: MSI kompilieren**
|
||||||
```bash
|
```bash
|
||||||
# WiX v3: Heat Tool verwenden
|
# WiX v6 Build
|
||||||
heat dir dist\DocuMentor -cg ProductComponents -gg -sfrag -srd -dr INSTALLFOLDER -out ProductFiles.wxs
|
wix build DocuMentor.wxs ProductFiles.wxs -o DocuMentor.msi
|
||||||
|
|
||||||
# WiX v4:
|
|
||||||
wix heat dir dist\DocuMentor -cg ProductComponents -gg -sfrag -srd -dr INSTALLFOLDER -out ProductFiles.wxs
|
|
||||||
```
|
```
|
||||||
|
|
||||||
**Schritt 5: MSI kompilieren**
|
**Hinweis**: Die Dateien `DocuMentor.wxs` und `generate_wix_files.py` sind bereits im Repository vorhanden und WiX v4/v6-kompatibel.
|
||||||
```bash
|
|
||||||
# WiX v3
|
|
||||||
candle DocuMentor.wxs -out obj\
|
|
||||||
light obj\DocuMentor.wixobj -out dist\installer\DocuMentor-0.1.0.msi -ext WixUIExtension
|
|
||||||
|
|
||||||
# WiX v4
|
**Schritt 4: MSI testen**
|
||||||
wix build DocuMentor.wxs -out dist\installer\DocuMentor-0.1.0.msi
|
|
||||||
```
|
|
||||||
|
|
||||||
**Schritt 6: MSI testen**
|
|
||||||
```bash
|
```bash
|
||||||
# Installation (als Administrator)
|
# Installation (als Administrator)
|
||||||
msiexec /i dist\installer\DocuMentor-0.1.0.msi
|
msiexec /i DocuMentor.msi
|
||||||
|
|
||||||
# Silent Installation für Deployment
|
# Silent Installation für Deployment
|
||||||
msiexec /i DocuMentor-0.1.0.msi /quiet /qn /norestart
|
msiexec /i DocuMentor.msi /quiet /qn /norestart
|
||||||
|
|
||||||
# Deinstallation
|
# Deinstallation
|
||||||
msiexec /x dist\installer\DocuMentor-0.1.0.msi
|
msiexec /x DocuMentor.msi
|
||||||
```
|
```
|
||||||
|
|
||||||
**MSI-Vorteile gegenüber Inno Setup:**
|
**MSI-Vorteile gegenüber Inno Setup:**
|
||||||
@@ -234,7 +99,7 @@ msiexec /x dist\installer\DocuMentor-0.1.0.msi
|
|||||||
|
|
||||||
**Build-Automatisierung (`build_msi.py`):**
|
**Build-Automatisierung (`build_msi.py`):**
|
||||||
```python
|
```python
|
||||||
"""WiX MSI Build-Skript für DocuMentor"""
|
"""WiX MSI Build-Skript für DocuMentor (WiX v6)"""
|
||||||
import subprocess
|
import subprocess
|
||||||
import sys
|
import sys
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
@@ -247,35 +112,22 @@ def build_msi():
|
|||||||
print("Fehler: PyInstaller Build nicht gefunden. Erst build_windows.py ausführen!")
|
print("Fehler: PyInstaller Build nicht gefunden. Erst build_windows.py ausführen!")
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
|
|
||||||
# Heat: Dateien harvesten
|
# Schritt 1: ProductFiles.wxs generieren (ersetzt heat)
|
||||||
print("Harvesting Dateien mit Heat...")
|
print("Generiere ProductFiles.wxs...")
|
||||||
subprocess.run([
|
subprocess.run([
|
||||||
"heat", "dir", str(dist_dir),
|
"uv", "run", "python", "generate_wix_files.py"
|
||||||
"-cg", "ProductComponents",
|
|
||||||
"-gg", "-sfrag", "-srd",
|
|
||||||
"-dr", "INSTALLFOLDER",
|
|
||||||
"-out", "ProductFiles.wxs"
|
|
||||||
], check=True)
|
], check=True)
|
||||||
|
|
||||||
# Candle: WXS zu WIXOBJ kompilieren
|
# Schritt 2: MSI kompilieren mit WiX v6
|
||||||
print("Kompiliere WXS-Dateien...")
|
print("Kompiliere MSI-Installer...")
|
||||||
subprocess.run([
|
subprocess.run([
|
||||||
"candle", "DocuMentor.wxs", "ProductFiles.wxs",
|
"wix", "build",
|
||||||
"-out", "obj\\"
|
"DocuMentor.wxs",
|
||||||
|
"ProductFiles.wxs",
|
||||||
|
"-o", "DocuMentor.msi"
|
||||||
], check=True)
|
], check=True)
|
||||||
|
|
||||||
# Light: WIXOBJ zu MSI linken
|
print("\n[OK] MSI erstellt: DocuMentor.msi")
|
||||||
print("Erzeuge MSI-Installer...")
|
|
||||||
(project_root / "dist" / "installer").mkdir(exist_ok=True)
|
|
||||||
subprocess.run([
|
|
||||||
"light",
|
|
||||||
"obj\\DocuMentor.wixobj",
|
|
||||||
"obj\\ProductFiles.wixobj",
|
|
||||||
"-out", "dist\\installer\\DocuMentor-0.1.0.msi",
|
|
||||||
"-ext", "WixUIExtension"
|
|
||||||
], check=True)
|
|
||||||
|
|
||||||
print("\n✅ MSI erstellt: dist\\installer\\DocuMentor-0.1.0.msi")
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
build_msi()
|
build_msi()
|
||||||
|
|||||||
@@ -0,0 +1,184 @@
|
|||||||
|
"""
|
||||||
|
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()
|
||||||
Reference in New Issue
Block a user