QuellCode/CentronERP war nur als Gitlink (Submodul-Referenz auf 79c1142) getrackt, ohne .gitmodules und ohne erreichbares Remote. Der Untersuchungsgegenstand der Versuchsreihe war damit nicht reproduzierbar gesichert: Ein Klon haette ein leeres Verzeichnis erhalten, und die Belege der 3.287 Anforderungen waeren nicht ueberpruefbar gewesen. Umstellung: - Historie nach c:\DEV\CentronERP_git_snapshot_79c1142 ausgelagert (vollstaendig lesbar, enthaelt 79c1142 und Vorgaenger 89ccfd6) - Gitlink aus dem Index entfernt - Dateiinhalt aufgenommen: 24.557 Dateien, rund 333 MB Die verschachtelte .gitignore der Codebasis gilt weiter, Build-Artefakte bleiben ausgeschlossen. Details in Versuche/Versuch_01/_Codebasis-Nachweis.md
260 lines
13 KiB
C#
260 lines
13 KiB
C#
using System;
|
|
using System.Collections.Generic;
|
|
using System.IO;
|
|
using System.Linq;
|
|
using System.Text.RegularExpressions;
|
|
using System.Xml.Linq;
|
|
|
|
namespace Centron.Scripts
|
|
{
|
|
public static class WXSHelper
|
|
{
|
|
private static readonly XNamespace Xmlns = @"http://schemas.microsoft.com/wix/2006/wi";
|
|
|
|
public static bool UpdateCentronSetupProductHeat()
|
|
{
|
|
return UpdateHeatWxs(CentronPaths.CentronNetInstaller.ProductHeat,
|
|
CustomizeComponentInWxs,
|
|
AllowIgnoreUpdatesToComponent,
|
|
(string.Empty, CentronPaths.CentronNet.PublishDirectory),
|
|
("de", CentronPaths.CentronNet.PublishedDeDirectory),
|
|
("en", CentronPaths.CentronNet.PublishedEnDirectory),
|
|
("Tools", CentronPaths.CentronNet.PublishedToolsDirectory));
|
|
|
|
void CustomizeComponentInWxs(string fileName, XElement componentElement)
|
|
{
|
|
if (fileName == "Files\\CentronConnections.xml")
|
|
{
|
|
componentElement.SetAttributeValue("NeverOverwrite", "yes");
|
|
componentElement.SetAttributeValue("Permanent", "yes");
|
|
}
|
|
}
|
|
}
|
|
|
|
public static bool UpdateWebServiceSetupProductHeat()
|
|
{
|
|
return UpdateHeatWxs(CentronPaths.WebServiceInstaller.ProductHeat,
|
|
CustomizeComponentInWxs,
|
|
AllowIgnoreUpdatesToComponent,
|
|
(string.Empty, CentronPaths.WebService.PublishDirectory),
|
|
("de", CentronPaths.WebService.PublishedDeDirectory),
|
|
("en", CentronPaths.WebService.PublishedEnDirectory),
|
|
("development", CentronPaths.WebService.PublishedDevelopmentDirectory),
|
|
("ConnectionManager", CentronPaths.WebService.PublishedConnectionManagerDirectory),
|
|
("ConnectionManager\\de", CentronPaths.WebService.PublishedConnectionManagerDeDirectory),
|
|
("ConnectionManager\\en", CentronPaths.WebService.PublishedConnectionManagerEnDirectory));
|
|
|
|
void CustomizeComponentInWxs(string fileName, XElement componentElement)
|
|
{
|
|
if (fileName == "Files\\Centron.Host.WindowsService.exe")
|
|
{
|
|
var serviceInstallElement = new XElement(Xmlns + "ServiceInstall",
|
|
new XAttribute("Id", "ServiceInstaller"),
|
|
new XAttribute("Type", "ownProcess"),
|
|
new XAttribute("Name", "CentronWebService"),
|
|
new XAttribute("DisplayName", "c-entron Web-Service"),
|
|
new XAttribute("Description", "c-entron Web-Service for access c-entron informations from outside"),
|
|
new XAttribute("Start", "auto"),
|
|
new XAttribute("ErrorControl", "normal"));
|
|
componentElement.Add(serviceInstallElement);
|
|
|
|
var serviceControlElement = new XElement(Xmlns + "ServiceControl",
|
|
new XAttribute("Id", "StartService"),
|
|
new XAttribute("Stop", "both"),
|
|
new XAttribute("Remove", "uninstall"),
|
|
new XAttribute("Name", "CentronWebService"),
|
|
new XAttribute("Wait", "no"),
|
|
new XAttribute("Start", "install"));
|
|
componentElement.Add(serviceControlElement);
|
|
|
|
// The Centron.Host.WindowsService.exe is referenced by this file-Id in the Product.wxs
|
|
// So it always should stay this id: fil04C6E9A1324A0A1878612A211EC8F58D
|
|
var fileElement = componentElement.Element(Xmlns + "File");
|
|
fileElement.SetAttributeValue("Id", "fil04C6E9A1324A0A1878612A211EC8F58D");
|
|
}
|
|
}
|
|
}
|
|
|
|
private static bool AllowIgnoreUpdatesToComponent(XElement componentElement)
|
|
{
|
|
var fileNamesToIgnore = new[]
|
|
{
|
|
// This file contains a version-number, and is different depending on which .NET SDK is installed
|
|
// So to allow building versions of the c-entron.NET and the Web-Services with .NET SDK version roll-forward,
|
|
// we have to allow different versions of this file too
|
|
// So we ignore WXS-heat updates to this file
|
|
"Files\\mscordaccore_amd64_amd64_(.*).dll",
|
|
"Files\\ConnectionManager\\mscordaccore_amd64_amd64_(.*).dll",
|
|
|
|
// These nuget-packages contain the build-version number in their file-name
|
|
// So we ignore WXS-heat updates to these files
|
|
$"Files\\development\\{CentronPaths.CentronCore.ProjectName}.(.*).nupkg",
|
|
$"Files\\development\\{CentronPaths.CentronInterfaces.ProjectName}.(.*).nupkg",
|
|
$"Files\\development\\{CentronPaths.CentronWebServicesCore.ProjectName}.(.*).nupkg",
|
|
|
|
// FastReport font cache files are excluded via ShouldExcludeFile, but we also need to
|
|
// suppress removal warnings when these files are removed from existing heat files
|
|
@"Files\\font(.*)\.list",
|
|
};
|
|
var fileName = componentElement.Element(Xmlns + "File")?.Attribute("Source")?.Value ?? string.Empty;
|
|
|
|
// Regex patterns see a single backslash as a escape-sequence, but in our case it's file-paths
|
|
// So we replace single backslashes with double backslashes
|
|
return fileNamesToIgnore.Any(f => Regex.IsMatch(fileName, f.Replace(@"\", @"\\")));
|
|
}
|
|
|
|
private static bool UpdateHeatWxs(string filePath, Action<string, XElement> componentModifier, Func<XElement, bool> allowIgnoreUpdatesToComponent, params (string directoryName, string directoryFilesPath)[] fileDirectories)
|
|
{
|
|
XDocument document;
|
|
|
|
using (var fileStream = new FileStream(filePath, FileMode.Open))
|
|
{
|
|
document = XDocument.Load(fileStream);
|
|
}
|
|
|
|
var fragments = document.Element(Xmlns + "Wix").Elements(Xmlns + "Fragment");
|
|
var directoryFragment = fragments.First(f => f.Element(Xmlns + "DirectoryRef") != null).Element(Xmlns + "DirectoryRef");
|
|
var componentGroupFragment = fragments.First(f => f.Element(Xmlns + "ComponentGroup") != null).Element(Xmlns + "ComponentGroup");
|
|
|
|
bool result = false;
|
|
foreach (var (directoryName, directoryFilesPath) in fileDirectories)
|
|
{
|
|
result = result | UpdateComponentsForDirectory(directoryFragment, componentGroupFragment, directoryName, directoryFilesPath, componentModifier, allowIgnoreUpdatesToComponent);
|
|
}
|
|
|
|
OrderComponents(componentGroupFragment);
|
|
|
|
using (var fileStream = new FileStream(filePath, FileMode.Truncate))
|
|
{
|
|
document.Save(fileStream);
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
private static bool UpdateComponentsForDirectory(XElement directoryFragment, XElement rootComponentGroup, string directoryName, string directoryFilePath, Action<string, XElement> componentModifier, Func<XElement, bool> allowIgnoreUpdatesToComponent)
|
|
{
|
|
string directoryGuid;
|
|
|
|
if (string.IsNullOrWhiteSpace(directoryName))
|
|
directoryGuid = directoryFragment.Attribute("Id").Value;
|
|
else
|
|
{
|
|
var parts = directoryName.Split(Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar);
|
|
var current = (XElement)directoryFragment;
|
|
foreach (var part in parts)
|
|
{
|
|
current = current.Elements(Xmlns + "Directory").First(f => f.Attribute("Name").Value == part);
|
|
}
|
|
directoryGuid = current.Attribute("Id").Value;
|
|
}
|
|
|
|
var files = Directory.Exists(directoryFilePath)
|
|
? Directory.GetFiles(directoryFilePath).Where(f => !ShouldExcludeFile(f)).ToList()
|
|
: new List<string>();
|
|
|
|
var removedFiles = RemoveComponentsForDirectory(directoryGuid, rootComponentGroup, files, allowIgnoreUpdatesToComponent);
|
|
var addedFiles = AddComponentsForFiles(directoryGuid, directoryName, rootComponentGroup, files, componentModifier, allowIgnoreUpdatesToComponent);
|
|
|
|
return removedFiles || addedFiles;
|
|
}
|
|
|
|
private static bool ShouldExcludeFile(string filePath)
|
|
{
|
|
var fileName = Path.GetFileName(filePath);
|
|
|
|
// FastReport creates a font cache file (e.g. font2022.1.6.list) containing machine-specific font paths
|
|
// This file is auto-generated at runtime and should not be included in the installer
|
|
if (Regex.IsMatch(fileName, @"^font.*\.list$", RegexOptions.IgnoreCase))
|
|
return true;
|
|
|
|
return false;
|
|
}
|
|
|
|
private static bool RemoveComponentsForDirectory(string directoryGuid, XElement rootComponentGroup, List<string> files, Func<XElement, bool> allowIgnoreUpdatesToComponent)
|
|
{
|
|
var fileNames = files
|
|
.Select(f => Path.GetFileName(f))
|
|
.ToList();
|
|
|
|
var components = rootComponentGroup
|
|
.Elements(Xmlns + "Component")
|
|
.Where(f =>
|
|
f.Attribute("Directory").Value == directoryGuid &&
|
|
fileNames.Contains(Path.GetFileName(f.Element(Xmlns + "File").Attribute("Source").Value)) == false)
|
|
.ToList();
|
|
|
|
foreach (var currentComponent in components)
|
|
{
|
|
if (!allowIgnoreUpdatesToComponent(currentComponent))
|
|
{
|
|
Console.WriteLine($"(ISSUE) Removed component: {currentComponent.Element(Xmlns + "File").Attribute("Source").Value}");
|
|
}
|
|
currentComponent.Remove();
|
|
}
|
|
|
|
return components.Any(f => allowIgnoreUpdatesToComponent(f) is false);
|
|
}
|
|
|
|
private static bool AddComponentsForFiles(string directoryGuid, string directoryName, XElement rootComponentGroup, IList<string> files, Action<string, XElement> componentModifier, Func<XElement, bool> allowIgnoreUpdatesToComponent)
|
|
{
|
|
var baseSource = Path.Combine("Files", directoryName);
|
|
|
|
bool FileIsInWxs(string f)
|
|
{
|
|
return rootComponentGroup
|
|
.Elements(Xmlns + "Component")
|
|
.Any(d =>
|
|
d.Attribute("Directory").Value == directoryGuid &&
|
|
d.Element(Xmlns + "File").Attribute("Source").Value == Path.Combine(baseSource, Path.GetFileName(f)));
|
|
}
|
|
|
|
var missingFiles = files
|
|
.Select(f => Path.GetFileName(f))
|
|
.Where(f => FileIsInWxs(f) == false)
|
|
.ToList();
|
|
var addedComponents = new List<XElement>();
|
|
|
|
foreach (var currentFile in missingFiles)
|
|
{
|
|
var fileSourcePath = Path.Combine(baseSource, currentFile);
|
|
|
|
var component = new XElement(Xmlns + "Component",
|
|
new XAttribute("Id", $"cmp{Guid.NewGuid().ToString("N").ToUpper()}"),
|
|
new XAttribute("Directory", directoryGuid),
|
|
new XAttribute("Guid", $"{Guid.NewGuid().ToString().ToUpper()}"),
|
|
new XElement(Xmlns + "File",
|
|
new XAttribute("Id", $"fil{Guid.NewGuid().ToString("N").ToUpper()}"),
|
|
new XAttribute("KeyPath", "yes"),
|
|
new XAttribute("Source", fileSourcePath)));
|
|
|
|
componentModifier?.Invoke(fileSourcePath, component);
|
|
|
|
rootComponentGroup.Add(component);
|
|
addedComponents.Add(component);
|
|
|
|
if (!allowIgnoreUpdatesToComponent(component))
|
|
{
|
|
Console.WriteLine($"(ISSUE) Missing component in heat file: {component.Element(Xmlns + "File").Attribute("Source").Value}");
|
|
}
|
|
}
|
|
|
|
return addedComponents.Any(f => allowIgnoreUpdatesToComponent(f) is false);
|
|
}
|
|
|
|
private static void OrderComponents(XElement componentGroupFragment)
|
|
{
|
|
var components = componentGroupFragment
|
|
.Elements(Xmlns + "Component")
|
|
.OrderBy(f => f.Element(Xmlns + "File").Attribute("Source").Value)
|
|
.ToList();
|
|
|
|
foreach (var component in components)
|
|
{
|
|
component.Remove();
|
|
componentGroupFragment.Add(component);
|
|
}
|
|
}
|
|
}
|
|
}
|