Codebasis als Dateien ins Arbeitsrepo statt als Gitlink

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
This commit is contained in:
Christoph Schwörer
2026-08-26 07:43:51 +02:00
parent 18edae75b6
commit f045b99a25
24664 changed files with 5846716 additions and 1 deletions
@@ -0,0 +1,13 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net10.0</TargetFramework>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Bullseye" Version="6.1.0" />
<PackageReference Include="CliWrap" Version="3.10.1" />
</ItemGroup>
</Project>
@@ -0,0 +1,20 @@
using System.IO;
using System.Text;
namespace Centron.Scripts
{
public static class CentronConnectionsHelper
{
public static void CreateCentronConnections(string directory)
{
string content = @"<?xml version=""1.0""?>
<ArrayOfConnectionFileItem xmlns:xsi=""http://www.w3.org/2001/XMLSchema-instance"" xmlns:xsd=""http://www.w3.org/2001/XMLSchema"">
</ArrayOfConnectionFileItem>";
var fileName = Path.Combine(directory, "CentronConnections.xml");
File.WriteAllText(fileName, content, Encoding.UTF8);
}
}
}
@@ -0,0 +1,181 @@
using System.IO;
using System.Linq;
namespace Centron.Scripts
{
public static class CentronPaths
{
public static string SlnDirectory
{
get
{
var currentFolder = new DirectoryInfo(Path.GetDirectoryName(typeof(CentronPaths).Assembly.Location));
return currentFolder.Parent.Parent.Parent.Parent.Parent.FullName;
}
}
public static string SlnPath => Path.Combine(SlnDirectory, "Centron.sln");
public static string ArtifactsDirectory => Path.Combine(SlnDirectory, "artifacts");
public static string NugetArtifactsDirectory => Path.Combine(ArtifactsDirectory, "nuget");
public static string DotNetToolsDirectory => Path.Combine(SlnDirectory, "dotnettools");
public static string DirectoryBuildProps => Path.Combine(SlnDirectory, "Directory.Build.props");
public static string[] ProjectDirectories => new[]
{
Path.Combine(SlnDirectory, "deployment", "centron", "CentronSetupProject"),
Path.Combine(SlnDirectory, "deployment", "centron", "WebServiceSetupProject"),
Path.Combine(SlnDirectory, "src", "apis", "Centron.Api.EbInterface"),
Path.Combine(SlnDirectory, "src", "apis", "Centron.Api.Gls"),
Path.Combine(SlnDirectory, "src", "apis", "Centron.APIs.CopDataAccess"),
Path.Combine(SlnDirectory, "src", "apis", "Centron.APIs.EgisDataAccess"),
Path.Combine(SlnDirectory, "src", "apis", "Centron.APIs.IcecatDataAccess"),
Path.Combine(SlnDirectory, "src", "apis", "Centron.APIs.ITscopeDataAccess"),
Path.Combine(SlnDirectory, "src", "backend", "Centron.BL"),
Path.Combine(SlnDirectory, "src", "backend", "Centron.Common"),
Path.Combine(SlnDirectory, "src", "backend", "Centron.DAO"),
Path.Combine(SlnDirectory, "src", "backend", "Centron.Entities"),
Path.Combine(SlnDirectory, "src", "backend", "Centron.Gateway"),
Path.Combine(SlnDirectory, "src", "backend", "Centron.Interfaces"),
Path.Combine(SlnDirectory, "src", "centron", "Centron.WPF.UI"),
Path.Combine(SlnDirectory, "src", "centron", "Centron.WPF.UI.Extension"),
Path.Combine(SlnDirectory, "src", "shared", "Centron.Controls"),
Path.Combine(SlnDirectory, "src", "shared", "Centron.Controls.Preview"),
Path.Combine(SlnDirectory, "src", "shared", "Centron.Core"),
Path.Combine(SlnDirectory, "src", "webservice", "Centron.Host"),
Path.Combine(SlnDirectory, "src", "webservice", "Centron.Host.Console"),
Path.Combine(SlnDirectory, "src", "webservice", "Centron.Host.WindowsService"),
Path.Combine(SlnDirectory, "src", "webservice", "c-entron.misc.ConnectionManager"),
Path.Combine(SlnDirectory, "src", "webservice", "Centron.WebServices.Core"),
Path.Combine(SlnDirectory, "tests", "apis", "Centron.APIs.CopDatabase.Tests"),
Path.Combine(SlnDirectory, "tests", "apis", "Centron.APIs.EgisDataAccess.Tests"),
Path.Combine(SlnDirectory, "tests", "apis", "Centron.APIs.IcecatDataAccess.Tests"),
Path.Combine(SlnDirectory, "tests", "apis", "Centron.APIs.ITscopeDataAccess.Tests"),
Path.Combine(SlnDirectory, "tests", "Centron.Tests.EndToEnd"),
};
public static string[] BinAndObjDirectories => new[]
{
ProjectDirectories.Select(f => Path.Combine(f, "bin")),
ProjectDirectories.Select(f => Path.Combine(f, "obj")),
}.SelectMany(f => f).ToArray();
public static class WebService
{
public static class ConnectionManager
{
public static string ProjectDirectory => Path.Combine(SlnDirectory, "src", "webservice", "c-entron.misc.ConnectionManager");
public static string CsProj => Path.Combine(ProjectDirectory, "c-entron.misc.ConnectionManager.csproj");
public static string PublishDirectory => Path.Combine(ProjectDirectory, "bin", "Release", "net10.0-windows", "win-x64", "publish");
}
public static string ProjectDirectory => Path.Combine(SlnDirectory, "src", "webservice", "Centron.Host.WindowsService");
public static string CsProj => Path.Combine(ProjectDirectory, "Centron.Host.WindowsService.csproj");
public static string PublishDirectory => Path.Combine(ProjectDirectory, "bin", "Release", "net10.0-windows", "win-x64", "publish");
public static string PublishedDeDirectory => Path.Combine(PublishDirectory, "de");
public static string PublishedEnDirectory => Path.Combine(PublishDirectory, "en");
public static string PublishedDevelopmentDirectory => Path.Combine(PublishDirectory, "development");
public static string PublishedConnectionManagerDirectory => Path.Combine(PublishDirectory, "ConnectionManager");
public static string PublishedConnectionManagerDeDirectory => Path.Combine(PublishedConnectionManagerDirectory, "de");
public static string PublishedConnectionManagerEnDirectory => Path.Combine(PublishedConnectionManagerDirectory, "en");
public static string PublishedWindowsServiceExe => Path.Combine(PublishDirectory, "Centron.Host.WindowsService.exe");
public static string PublishedWindowsServiceDll => Path.Combine(PublishDirectory, "Centron.Host.WindowsService.dll");
public static string PublishedConnectionManagerExe => Path.Combine(PublishedConnectionManagerDirectory, "c-entron Connection Manager.exe");
public static string PublishedConnectionManagerDll => Path.Combine(PublishedConnectionManagerDirectory, "c-entron Connection Manager.dll");
public static string PublishedCentronInterfacesDll => Path.Combine(PublishDirectory, "Centron.Interfaces.dll");
public static string PublishedCentronWebServicesCoreDll => Path.Combine(PublishDirectory, "Centron.WebServices.Core.dll");
public static string PublishedCentronCoreDll => Path.Combine(PublishDirectory, "Centron.Core.dll");
}
public static class WebServiceInstaller
{
public static string DeploymentDirectory => Path.Combine(SlnDirectory, "deployment", "centron");
public static string WixProjDirectory => Path.Combine(DeploymentDirectory, "WebServiceSetupProject");
public static string WixProj => Path.Combine(WixProjDirectory, "WebServiceSetupProject.wixproj");
public static string InstallerInputDirectory => Path.Combine(WixProjDirectory, "Files");
public static string PublishDirectory => Path.Combine(WixProjDirectory, "bin", "Release");
public static string ArtifactsFile => Path.Combine(ArtifactsDirectory, "c-entron Web-Service Installer.zip");
public static string PublishedInstallerMsi => Path.Combine(PublishDirectory, "c-entron Web-Service Installer.msi");
public static string ProductHeat => Path.Combine(WixProjDirectory, "WebServiceProductHeat.wxs");
}
public static class WebServiceLinux
{
public static string ProjectDirectory => Path.Combine(SlnDirectory, "src", "webservice", "Centron.Host.Console");
public static string CsProj => Path.Combine(ProjectDirectory, "Centron.Host.Console.csproj");
public static string PublishDirectory => Path.Combine(ProjectDirectory, "bin", "Release", "net10.0", "linux-x64", "publish");
public static string PublishedConsoleDll => Path.Combine(PublishDirectory, "Centron.Host.Console.dll");
public static string PublishedCentronHostConsoleConfigFile => Path.Combine(PublishDirectory, "Centron.Host.Console.dll.config");
public static string ArtifactsFile => Path.Combine(ArtifactsDirectory, "c-entron Web-Service Linux.zip");
public static string DebugSimpleWebServiceConfigPath => Path.Combine(ProjectDirectory, "bin", "Debug", "net10.0", "SimpleWebServiceConfig.txt");
}
public static class CentronNet
{
public static string ProjectDirectory => Path.Combine(SlnDirectory, "src", "centron", "Centron.WPF.UI");
public static string CsProj => Path.Combine(ProjectDirectory, "Centron.WPF.UI.csproj");
public static string PublishDirectory => Path.Combine(ProjectDirectory, "bin", "Release", "net10.0-windows", "win-x64", "publish");
public static string PublishedDeDirectory => Path.Combine(PublishDirectory, "de");
public static string PublishedEnDirectory => Path.Combine(PublishDirectory, "en");
public static string PublishedCentronNetExe => Path.Combine(PublishDirectory, "c-entron 2.0.exe");
public static string PublishedCentronNetDll => Path.Combine(PublishDirectory, "c-entron 2.0.dll");
public static string PublishedToolsDirectory => Path.Combine(PublishDirectory, "Tools");
public static string DebugCentronConnectionsPath => Path.Combine(ProjectDirectory, "bin", "Debug", "net10.0-windows", "CentronConnections.xml");
}
public static class CentronNetInstaller
{
public static string WixProjDirectory => Path.Combine(SlnDirectory, "deployment", "centron", "CentronSetupProject");
public static string WixProj => Path.Combine(WixProjDirectory, "CentronSetupProject.wixproj");
public static string InstallerInputDirectory => Path.Combine(WixProjDirectory, "Files");
public static string PublishDirectory => Path.Combine(WixProjDirectory, "bin", "Release");
public static string ArtifactsFile => Path.Combine(ArtifactsDirectory, "c-entron.NET Installer.zip");
public static string PublishedInstallerMsi => Path.Combine(PublishDirectory, "c-entron.NET Installer.msi");
public static string ProductHeat => Path.Combine(WixProjDirectory, "CentronProductHeat.wxs");
}
public static class CentronInterfaces
{
public static string ProjectName => "Centron.Interfaces";
public static string CsProj => Path.Combine(SlnDirectory, "src", "backend", ProjectName, $"{ProjectName}.csproj");
public static string PublishDirectory => Path.Combine(Path.GetDirectoryName(CsProj), "bin", "Release");
}
public static class CentronWebServicesCore
{
public static string ProjectName => "Centron.WebServices.Core";
public static string CsProj => Path.Combine(SlnDirectory, "src", "webservice", ProjectName, $"{ProjectName}.csproj");
public static string PublishDirectory => Path.Combine(Path.GetDirectoryName(CsProj), "bin", "Release");
}
public static class CentronCore
{
public static string ProjectName => "Centron.Core";
public static string CsProj => Path.Combine(SlnDirectory, "src", "shared", ProjectName, $"{ProjectName}.csproj");
public static string PublishDirectory => Path.Combine(Path.GetDirectoryName(CsProj), "bin", "Release");
}
public static class CentronControls
{
public static string CsProj => Path.Combine(SlnDirectory, "src", "shared", "Centron.Controls", "Centron.Controls.csproj");
public static string PublishDirectory => Path.Combine(Path.GetDirectoryName(CsProj), "bin", "Release");
}
public static class EndToEndTests
{
public static string CsProj => Path.Combine(SlnDirectory, "tests", "Centron.Tests.EndToEnd", "Centron.Tests.EndToEnd.csproj");
public static string ArtifactsDirectory => Path.Combine(CentronPaths.ArtifactsDirectory, "EndToEndTests");
public static string ArtifactsFileName => "TestResults.trx";
}
public static class DependencyGraph
{
public static string CentronWebServiceArtifactsFile => Path.Combine(ArtifactsDirectory, "c-entron Web-Service Dependencies.txt");
public static string CentronNetArtifactsFile => Path.Combine(ArtifactsDirectory, "c-entron.NET Dependencies.txt");
}
}
}
@@ -0,0 +1,191 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Net.Http;
using System.Text;
using System.Text.Json;
using System.Xml.Linq;
using static Centron.Scripts.RunHelper;
using static Centron.Scripts.CentronPaths;
namespace Centron.Scripts;
public static class DependencyGraph
{
public record Dependency(string Name, string Version, string ProjectUrl, string License);
public static List<Dependency> ReadDependencyGraphs(params string[] projectFilePaths)
{
return projectFilePaths.SelectMany(ReadDependencyGraph).Distinct().ToList();
}
public static List<Dependency> ReadDependencyGraph(string projectFilePath)
{
var projectAssetsJsonFilePath = Path.Combine(Path.GetDirectoryName(projectFilePath), "obj", "project.assets.json");
var projectAssetsJsonContent = File.ReadAllText(projectAssetsJsonFilePath);
var filter = new Func<Dependency, bool>[]
{
IsMicrosoftPackage,
IsCentronPackage,
IsRiverbirdPackage,
IsRuntimePackage,
IsLanguagePackage,
IsNetStandardLibraryPackage,
};
var dependencies = JsonDocument.Parse(projectAssetsJsonContent)
.RootElement.GetProperty("targets")
.EnumerateObject()
.SelectMany(f => f.Value.EnumerateObject()
.Select(f => f.Name.Split('/'))
.Select(f => new Dependency(f[0], f[1], null, null)))
.Where(f => filter.All(d => d(f) is false))
.Select(f => LoadAdditionalInfo(f))
.Distinct() // We might have duplicates if multiple targets are found in the project.assets.json file, so remove them
.ToList();
return dependencies;
}
private static readonly HttpClient _httpClient = new();
private static Dependency LoadAdditionalInfo(Dependency dependency)
{
if (dependency.Name.StartsWith("DevExpress"))
return dependency with { ProjectUrl = "https://www.devexpress.com/", License = "DevExpress" };
if (dependency.Name.StartsWith("FastReport"))
return dependency with { ProjectUrl = "https://www.fast-report.com/", License = "FastReport" };
try
{
// See: https://docs.microsoft.com/en-us/nuget/api/package-base-address-resource#download-package-manifest-nuspec
using var request = new HttpRequestMessage(HttpMethod.Get, $"https://api.nuget.org/v3-flatcontainer/{dependency.Name.ToLower()}/{dependency.Version}/{dependency.Name.ToLower()}.nuspec");
using var response = _httpClient.Send(request);
using var responseStream = response.Content.ReadAsStream();
using var reader = new StreamReader(responseStream);
var nuspecXml = reader.ReadToEnd();
var xmlDocument = XDocument.Parse(nuspecXml);
var xmlNamespace = "{" + xmlDocument.Root.GetDefaultNamespace() + "}";
var metadataElement = xmlDocument.Root.Element($"{xmlNamespace}metadata");
var license = metadataElement.Element($"{xmlNamespace}license")?.Value;
var licenseUrl = metadataElement.Element($"{xmlNamespace}licenseUrl")?.Value;
license = TryGuessLicense(dependency, license, licenseUrl) ?? license;
var projectUrl = metadataElement.Element($"{xmlNamespace}projectUrl")?.Value;
var sourceCodeUrl = metadataElement.Element($"{xmlNamespace}repository")?.Attribute("url")?.Value;
var url = string.IsNullOrWhiteSpace(projectUrl) is false ? projectUrl : sourceCodeUrl;
return dependency with { ProjectUrl = url, License = license };
}
catch
{
// Something went wrong, but we don't want the build to fail because of that
return dependency;
}
}
private static string TryGuessLicense(Dependency dependency, string license, string licenseUrl)
{
if (dependency is { Name: "Antlr3.Runtime", Version: "3.5.1" } &&
licenseUrl is "https://raw.githubusercontent.com/antlr/antlrcs/master/LICENSE.txt")
return "BSD-3-Clause";
if (dependency is { Name: "AutoMapper", Version: "8.1.1" } &&
licenseUrl is "https://github.com/AutoMapper/AutoMapper/blob/master/LICENSE.txt")
return "MIT";
if (dependency is { Name: "Castle.Core", Version: "4.4.1" } &&
licenseUrl is "http://www.apache.org/licenses/LICENSE-2.0.html")
return "Apache-2.0";
if (dependency is { Name: "FluentNHibernate", Version: "3.2.1" } &&
licenseUrl is "https://aka.ms/deprecateLicenseUrl")
return "BSD-3-Clause";
if (dependency is { Name: "Iesi.Collections", Version: "4.0.4" } &&
licenseUrl is null)
return "Public Domain";
if (dependency is { Name: "libfintx", Version: "1.1.0" } &&
licenseUrl is "https://opensource.org/license/LGPL-3.0")
return "LGPL-3.0";
if (dependency is { Name: "Remotion.Linq", Version: "2.2.0" } &&
licenseUrl is "http://opensource.org/licenses/Apache-2.0")
return "Apache-2.0";
if (dependency is { Name: "Remotion.Linq.EagerFetching", Version: "2.2.0" } &&
licenseUrl is "http://opensource.org/licenses/LGPL-2.1")
return "LGPL-2.1";
if (dependency is { Name: "SSH.NET", Version: "2020.0.2" } &&
licenseUrl is "https://github.com/sshnet/SSH.NET/blob/master/LICENSE")
return "MIT";
if (dependency is { Name: "SshNet.Security.Cryptography", Version: "1.3.0" } &&
licenseUrl is "https://github.com/sshnet/Cryptography/blob/master/LICENSE")
return "MIT";
if (dependency is { Name: "Castle.Windsor", Version: "5.0.1" } &&
licenseUrl is "http://www.apache.org/licenses/LICENSE-2.0.html")
return "Apache-2.0";
if (dependency is { Name: "OpenMcdf", Version: "2.3.0" } &&
licenseUrl is "https://opensource.org/licenses/MPL-2.0")
return "MPL-2.0";
if (dependency is { Name: "RtfPipe", Version: "2.0.7677.4303" } &&
licenseUrl is null)
return "MIT";
if (dependency is { Name: "Simple-MAPI.NET", Version: "1.2.1" } &&
licenseUrl is "https://aka.ms/deprecateLicenseUrl")
return "MIT";
return null;
}
private static bool IsMicrosoftPackage(Dependency package)
{
return package.Name.StartsWith("Microsoft") ||
package.Name.StartsWith("System");
}
private static bool IsCentronPackage(Dependency package)
{
return package.Name.StartsWith("Centron");
}
private static bool IsRiverbirdPackage(Dependency package)
{
return package.Name.StartsWith("Riverbird");
}
private static bool IsRuntimePackage(Dependency package)
{
return package.Name.StartsWith("runtime");
}
private static bool IsLanguagePackage(Dependency package)
{
return package.Name.EndsWith(".de");
}
private static bool IsNetStandardLibraryPackage(Dependency package)
{
return package.Name == "NETStandard.Library";
}
public static void Write(List<Dependency> dependencies, string filePath)
{
var content = new StringBuilder();
content.AppendLine($"{"Dependency",-50}{"Versionsnummer",-15}{"Lizenz",-15}{"URL",-100}");
foreach (var dependency in dependencies.OrderBy(f => f.Name).ThenBy(f => f.License))
{
content.AppendLine($"{dependency.Name,-50}{dependency.Version,-15}{dependency.License,-15}{dependency.ProjectUrl,-100}");
}
if (!Directory.Exists(Path.GetDirectoryName(filePath)))
Directory.CreateDirectory(Path.GetDirectoryName(filePath));
File.WriteAllText(filePath, content.ToString());
}
}
@@ -0,0 +1,49 @@
using System;
namespace Centron.Scripts
{
public static class EnvironmentHelper
{
public static string GetEnvironmentVariable(string name)
{
var targets = new[]
{
EnvironmentVariableTarget.Process,
EnvironmentVariableTarget.User,
EnvironmentVariableTarget.Machine
};
foreach (var target in targets)
{
var value = Environment.GetEnvironmentVariable(name, target);
if (string.IsNullOrWhiteSpace(value) == false)
return value;
}
return null;
}
public static bool IsDevBuild()
{
// IsDevBuild is true by default
// It is only supposed to be false, when it's set through the environment variable to false
string environmentVariable = GetEnvironmentVariable(EnvironmentVariables.IsDevBuild);
bool isDevBuild = bool.TryParse(environmentVariable, out var f) ? f : true;
return isDevBuild;
}
public static bool RunningInAzurePipelines()
{
string environmentVariable = GetEnvironmentVariable(EnvironmentVariables.RunningInAzurePipelines);
return bool.TryParse(environmentVariable, out var f) ? f : false;
}
public static bool UseLongTimeout()
{
string environmentVariable = GetEnvironmentVariable(EnvironmentVariables.UseLongTimeout);
return bool.TryParse(environmentVariable, out var f) ? f : false;
}
}
}
@@ -0,0 +1,13 @@
namespace Centron.Scripts
{
public static class EnvironmentVariables
{
public static string IsDevBuild => "CENTRON_BUILD_IS_DEV_BUILD";
public static string CodeSigningCertificateCentron => "CENTRON_BUILD_CODE_SIGNING_CERTIFICATE";
public static string CodeSigningCertificatePasswordCentron => "CENTRON_BUILD_CODE_SIGNING_CERTIFICATE_PASSWORD";
public static string RunningInAzurePipelines => "CENTRON_BUILD_RUNNING_IN_AZURE_PIPELINE";
public static string UseLongTimeout => "CENTRON_BUILD_USE_LONG_TIMEOUT";
}
}
@@ -0,0 +1,149 @@
using System;
using System.IO;
using System.IO.Compression;
using System.Linq;
using System.Text;
using System.Text.RegularExpressions;
using System.Xml;
using System.Xml.Linq;
using System.Xml.Serialization;
namespace Centron.Scripts
{
public static class FileHelper
{
public static void DeleteSubDirectoriesOtherThan(string directory, params string[] allowedSubDirectories)
{
var allowedSubDirectoriesFullPaths = allowedSubDirectories.Select(f => Path.Combine(directory, f)).ToList();
var allSubDirectories = Directory.GetDirectories(directory);
foreach (var subDirectory in allSubDirectories)
{
if (allowedSubDirectoriesFullPaths.Contains(subDirectory) == false)
{
DeleteDirectory(subDirectory);
}
}
}
public static void DeleteDirectory(string directory)
{
if (Directory.Exists(directory) == false)
return;
Directory.Delete(directory, recursive: true);
}
public static void MoveDirectory(string fromDirectory, string toDirectory)
{
MoveFiles(fromDirectory, toDirectory, "*.*");
}
public static void MoveFiles(string fromDirectory, string toDirectory, string filePattern = "*.*", SearchOption searchOption = SearchOption.AllDirectories, bool skipAlreadyExistingFiles = false)
{
MoveOrCopyFilesInternal((file, targetFile) => File.Move(file, targetFile), fromDirectory, toDirectory, filePattern, searchOption, skipAlreadyExistingFiles);
}
public static void CopyFiles(string fromDirectory, string toDirectory, string filePattern = "*.*", SearchOption searchOption = SearchOption.AllDirectories, bool skipAlreadyExistingFiles = false)
{
MoveOrCopyFilesInternal((file, targetFile) => File.Copy(file, targetFile), fromDirectory, toDirectory, filePattern, searchOption, skipAlreadyExistingFiles);
}
private static void MoveOrCopyFilesInternal(Action<string, string> action, string fromDirectory, string toDirectory, string filePattern = "*.*", SearchOption searchOption = SearchOption.AllDirectories, bool skipAlreadyExistingFiles = false)
{
if (Directory.Exists(toDirectory) == false)
Directory.CreateDirectory(toDirectory);
foreach (string file in Directory.GetFiles(fromDirectory, filePattern, searchOption))
{
var relativePath = Path.GetRelativePath(fromDirectory, file);
var targetFile = Path.Combine(toDirectory, relativePath);
var targetDirectory = Path.GetDirectoryName(targetFile);
if (Directory.Exists(targetDirectory) == false)
Directory.CreateDirectory(targetDirectory);
if (skipAlreadyExistingFiles && File.Exists(targetFile))
continue;
action(file, targetFile);
}
}
public static void DeleteFiles(string directory, string filePattern)
{
foreach (var file in Directory.GetFiles(directory, filePattern))
{
File.Delete(file);
}
}
public static void RenameFile(string directory, string fileName, string newFileName)
{
File.Move(Path.Combine(directory, fileName), Path.Combine(directory, newFileName));
}
public static void ZipDirectory(string directory, string outputFilePath)
{
if (File.Exists(outputFilePath))
File.Delete(outputFilePath);
var outputDirectory = Path.GetDirectoryName(outputFilePath);
if (Directory.Exists(outputDirectory) == false)
Directory.CreateDirectory(outputDirectory);
ZipFile.CreateFromDirectory(directory, outputFilePath);
}
public static void ExtractZip(string archive, string directory)
{
ZipFile.ExtractToDirectory(archive, directory);
}
public static void FixNLogConfig(string directory)
{
var nlogConfigPath = Path.Combine(directory, "nlog.config");
var document = XDocument.Load(nlogConfigPath);
var loggerElements = document.Root
.Element(XName.Get("rules", "http://www.nlog-project.org/schemas/NLog.xsd"))
.Elements(XName.Get("logger", "http://www.nlog-project.org/schemas/NLog.xsd"));
foreach (var logger in loggerElements)
{
logger.SetAttributeValue("minLevel", "WARN");
}
document.Save(nlogConfigPath, SaveOptions.None);
}
public static void FixNLogConfigRiverbirdPath(string directory)
{
var nlogConfigPath = Path.Combine(directory, "nlog.config");
var document = XDocument.Load(nlogConfigPath);
var variables = document.Root
.Elements(XName.Get("variable", "http://www.nlog-project.org/schemas/NLog.xsd"));
foreach (var variable in variables)
{
if (!variable.Attribute("name").Value.Equals("logDirectory", StringComparison.InvariantCultureIgnoreCase))
continue;
variable.SetAttributeValue("value", "${specialfolder:folder=CommonApplicationData}/Riverbird/Riverbird Web-Service/Logs");
}
document.Save(nlogConfigPath, SaveOptions.None);
}
public static void UpdateVersionInfo(string path, string version, string commitId, bool isDevBuild)
{
var buildProps = XElement.Load(path);
var propGroup = buildProps.Elements("PropertyGroup").First();
propGroup.Element("IsDevBuild").SetValue(isDevBuild);
propGroup.Element("Version").SetValue(version);
propGroup.Element("GitCommitId").SetValue(commitId);
buildProps.Save(path);
}
}
}
@@ -0,0 +1,328 @@
using System;
using System.IO;
using System.Linq;
using System.Text.Json;
using System.Threading.Tasks;
using static Bullseye.Targets;
using static Centron.Scripts.RunHelper;
using static Centron.Scripts.FileHelper;
using static Centron.Scripts.CentronPaths;
using static Centron.Scripts.CentronConnectionsHelper;
namespace Centron.Scripts
{
public class Program
{
static async Task Main(string[] args)
{
Target("clean", () =>
{
var centronConnectionsBytes = File.Exists(CentronNet.DebugCentronConnectionsPath)
? File.ReadAllBytes(CentronNet.DebugCentronConnectionsPath) : null;
var simpleWebServiceConfigBytes = File.Exists(WebServiceLinux.DebugSimpleWebServiceConfigPath)
? File.ReadAllBytes(WebServiceLinux.DebugSimpleWebServiceConfigPath) : null;
DeleteDirectory(ArtifactsDirectory);
Console.WriteLine("Cleaned artifacts directory");
DeleteDirectory(DotNetToolsDirectory);
Console.WriteLine("Cleaned dotnet tools directory");
DeleteDirectory(WebServiceInstaller.InstallerInputDirectory);
Console.WriteLine("Cleaned c-entron Web-Service installer input directory");
DeleteDirectory(CentronNetInstaller.InstallerInputDirectory);
Console.WriteLine("Cleaned c-entron.NET installer input directory");
foreach (var directory in BinAndObjDirectories)
{
DeleteDirectory(directory);
}
Console.WriteLine("Cleaned all bin and obj directories");
if (centronConnectionsBytes != null)
{
Directory.CreateDirectory(Path.GetDirectoryName(CentronNet.DebugCentronConnectionsPath));
File.WriteAllBytes(CentronNet.DebugCentronConnectionsPath, centronConnectionsBytes);
Console.WriteLine("Preserved CentronConnections.xml");
}
if (simpleWebServiceConfigBytes != null)
{
Directory.CreateDirectory(Path.GetDirectoryName(WebServiceLinux.DebugSimpleWebServiceConfigPath));
File.WriteAllBytes(WebServiceLinux.DebugSimpleWebServiceConfigPath, simpleWebServiceConfigBytes);
Console.WriteLine("Preserved SimpleWebServiceConfig.txt");
}
});
Target("setup-versioning", ["clean"], () =>
{
RunDotNet($"tool install nbgv --version 3.0.19-beta --tool-path \"{DotNetToolsDirectory}\"");
var version = ReadDotNetTool("nbgv", "get-version -v Version", WebServiceInstaller.DeploymentDirectory).Trim();
var commitId = ReadDotNetTool("nbgv", "get-version -v GitCommitIdShort", WebServiceInstaller.DeploymentDirectory).Trim();
var isDevBuild = EnvironmentHelper.IsDevBuild();
UpdateVersionInfo(DirectoryBuildProps, version, commitId, isDevBuild);
if (isDevBuild)
Console.WriteLine($"Dev version is {version}");
else
Console.WriteLine($"Current version is {version}");
});
Target("create-nuget-packages", ["setup-versioning"], () =>
{
RunDotNet($"pack \"{CentronCore.CsProj}\" -c Release -nodeReuse:false");
MoveFiles(CentronCore.PublishDirectory, NugetArtifactsDirectory, "*.nupkg", SearchOption.TopDirectoryOnly);
RunDotNet($"pack \"{CentronInterfaces.CsProj}\" -c Release -nodeReuse:false");
MoveFiles(CentronInterfaces.PublishDirectory, NugetArtifactsDirectory, "*.nupkg", SearchOption.TopDirectoryOnly);
RunDotNet($"pack \"{CentronWebServicesCore.CsProj}\" -c Release -nodeReuse:false");
MoveFiles(CentronWebServicesCore.PublishDirectory, NugetArtifactsDirectory, "*.nupkg", SearchOption.TopDirectoryOnly);
RunDotNet($"pack \"{CentronControls.CsProj}\" -c Release -nodeReuse:false");
MoveFiles(CentronControls.PublishDirectory, NugetArtifactsDirectory, "*.nupkg", SearchOption.TopDirectoryOnly);
Console.WriteLine("Created the nuget package artifacts");
});
Target("build-web-service", ["setup-versioning", "create-nuget-packages"], () =>
{
RunDotNet($"publish \"{WebService.ConnectionManager.CsProj}\" -r win-x64 --self-contained true -c Release -f net10.0-windows -nodeReuse:false");
Console.WriteLine("Finished building the connection-manager");
RunDotNet($"publish \"{WebService.CsProj}\" -r win-x64 --self-contained true -c Release -f net10.0-windows -nodeReuse:false");
Console.WriteLine("Finished building the web-service");
// Move connection-manager into its own subdirectory to avoid DLL conflicts.
// Both apps are self-contained and carry their own runtime independently.
MoveDirectory(WebService.ConnectionManager.PublishDirectory, WebService.PublishedConnectionManagerDirectory);
Console.WriteLine("Moved connection-manager into ConnectionManager subdirectory");
DeleteSubDirectoriesOtherThan(WebService.PublishDirectory, "de", "en", "ConnectionManager");
Console.WriteLine("Deleted unneeded files");
FixNLogConfig(WebService.PublishDirectory);
Console.WriteLine("Fixed the nlog.config");
CopyFiles(NugetArtifactsDirectory, WebService.PublishedDevelopmentDirectory, $"{CentronCore.ProjectName}*.nupkg");
CopyFiles(NugetArtifactsDirectory, WebService.PublishedDevelopmentDirectory, $"{CentronInterfaces.ProjectName}*.nupkg");
CopyFiles(NugetArtifactsDirectory, WebService.PublishedDevelopmentDirectory, $"{CentronWebServicesCore.ProjectName}*.nupkg");
Console.WriteLine("Copied the nuget-packages into the development directory");
var dependencies = DependencyGraph.ReadDependencyGraphs(WebService.CsProj, WebService.ConnectionManager.CsProj);
DependencyGraph.Write(dependencies, CentronPaths.DependencyGraph.CentronWebServiceArtifactsFile);
});
Target("build-web-service-installer", ["build-web-service"], () =>
{
if (WXSHelper.UpdateWebServiceSetupProductHeat())
throw new Exception($"The installer product heat {WebServiceInstaller.ProductHeat} is not up to date. Execute the target update-installer-product-heat and commit the changes.");
MoveDirectory(WebService.PublishDirectory, WebServiceInstaller.InstallerInputDirectory);
Console.WriteLine("Copied the web-service files into the web-service installer input directory");
RunMsBuild($"\"{WebServiceInstaller.WixProj}\" /t:Build /p:Configuration=Release /m -nodeReuse:false");
Console.WriteLine("Created the web-service installer");
DeleteFiles(WebServiceInstaller.PublishDirectory, "*.wixpdb");
Console.WriteLine("Deleted unneeded files");
ZipDirectory(WebServiceInstaller.PublishDirectory, WebServiceInstaller.ArtifactsFile);
Console.WriteLine("Created the web-service installer artifact");
});
Target("build-centron-net", ["setup-versioning"], () =>
{
RunDotNet($"publish \"{CentronNet.CsProj}\" -r win-x64 --self-contained true -c Release -f net10.0-windows -nodeReuse:false");
Console.WriteLine("Finished building the c-entron.NET");
DeleteSubDirectoriesOtherThan(CentronNet.PublishDirectory, "de", "en", "Tools");
Console.WriteLine("Deleted unneeded files");
FixNLogConfig(CentronNet.PublishDirectory);
Console.WriteLine("Fixed the nlog.config");
CreateCentronConnections(CentronNet.PublishDirectory);
Console.WriteLine("Created the CentronConnections.xml");
var dependencies = DependencyGraph.ReadDependencyGraph(CentronNet.CsProj);
DependencyGraph.Write(dependencies, CentronPaths.DependencyGraph.CentronNetArtifactsFile);
});
Target("build-centron-net-installer", ["build-centron-net"], () =>
{
if (WXSHelper.UpdateCentronSetupProductHeat())
throw new Exception($"The installer product heat {CentronNetInstaller.ProductHeat} is not up to date. Execute the target update-installer-product-heat and commit the changes.");
MoveDirectory(CentronNet.PublishDirectory, CentronNetInstaller.InstallerInputDirectory);
Console.WriteLine("Copied the c-entron.NET files into the c-entron.NET installer input directory");
RunMsBuild($"\"{CentronNetInstaller.WixProj}\" /t:Build /p:Configuration=Release /m -nodeReuse:false");
Console.WriteLine("Created the c-entron.NET installer");
DeleteFiles(CentronNetInstaller.PublishDirectory, "*.wixpdb");
Console.WriteLine("Deleted unneeded files");
ZipDirectory(CentronNetInstaller.PublishDirectory, CentronNetInstaller.ArtifactsFile);
Console.WriteLine("Created the c-entron.NET installer artifact");
});
Target("build-web-service-only", () =>
{
RunDotNet($"publish \"{WebService.ConnectionManager.CsProj}\" -r win-x64 --self-contained true -c Release -f net10.0-windows -nodeReuse:false");
Console.WriteLine("Finished building the connection-manager");
RunDotNet($"publish \"{WebService.CsProj}\" -r win-x64 --self-contained true -c Release -f net10.0-windows -nodeReuse:false");
Console.WriteLine("Finished building the web-service");
// Move connection-manager into its own subdirectory to avoid DLL conflicts.
// Both apps are self-contained and carry their own runtime independently.
MoveDirectory(WebService.ConnectionManager.PublishDirectory, WebService.PublishedConnectionManagerDirectory);
Console.WriteLine("Moved connection-manager into ConnectionManager subdirectory");
DeleteSubDirectoriesOtherThan(WebService.PublishDirectory, "de", "en", "ConnectionManager");
Console.WriteLine("Deleted unneeded files");
FixNLogConfig(WebService.PublishDirectory);
Console.WriteLine("Fixed the nlog.config");
CopyFiles(NugetArtifactsDirectory, WebService.PublishedDevelopmentDirectory, $"{CentronCore.ProjectName}*.nupkg");
CopyFiles(NugetArtifactsDirectory, WebService.PublishedDevelopmentDirectory, $"{CentronInterfaces.ProjectName}*.nupkg");
CopyFiles(NugetArtifactsDirectory, WebService.PublishedDevelopmentDirectory, $"{CentronWebServicesCore.ProjectName}*.nupkg");
Console.WriteLine("Copied the nuget-packages into the development directory");
});
Target("build-web-service-installer-only", () =>
{
if (WXSHelper.UpdateWebServiceSetupProductHeat())
throw new Exception($"The installer product heat {WebServiceInstaller.ProductHeat} is not up to date. Execute the target update-installer-product-heat and commit the changes.");
MoveDirectory(WebService.PublishDirectory, WebServiceInstaller.InstallerInputDirectory);
Console.WriteLine("Copied the web-service files into the web-service installer input directory");
RunMsBuild($"\"{WebServiceInstaller.WixProj}\" /t:Build /p:Configuration=Release /m -nodeReuse:false");
Console.WriteLine("Created the web-service installer");
DeleteFiles(WebServiceInstaller.PublishDirectory, "*.wixpdb");
Console.WriteLine("Deleted unneeded files");
ZipDirectory(WebServiceInstaller.PublishDirectory, WebServiceInstaller.ArtifactsFile);
Console.WriteLine("Created the web-service installer artifact");
});
Target("build-centron-net-only", () =>
{
RunDotNet($"publish \"{CentronNet.CsProj}\" -r win-x64 --self-contained true -c Release -f net10.0-windows -nodeReuse:false");
Console.WriteLine("Finished building the c-entron.NET");
DeleteSubDirectoriesOtherThan(CentronNet.PublishDirectory, "de", "en", "Tools");
Console.WriteLine("Deleted unneeded files");
FixNLogConfig(CentronNet.PublishDirectory);
Console.WriteLine("Fixed the nlog.config");
CreateCentronConnections(CentronNet.PublishDirectory);
Console.WriteLine("Created the CentronConnections.xml");
});
Target("build-centron-net-installer-only", () =>
{
if (WXSHelper.UpdateCentronSetupProductHeat())
throw new Exception($"The installer product heat {CentronNetInstaller.ProductHeat} is not up to date. Execute the target update-installer-product-heat and commit the changes.");
MoveDirectory(CentronNet.PublishDirectory, CentronNetInstaller.InstallerInputDirectory);
Console.WriteLine("Copied the c-entron.NET files into the c-entron.NET installer input directory");
RunMsBuild($"\"{CentronNetInstaller.WixProj}\" /t:Build /p:Configuration=Release /m -nodeReuse:false");
Console.WriteLine("Created the c-entron.NET installer");
DeleteFiles(CentronNetInstaller.PublishDirectory, "*.wixpdb");
Console.WriteLine("Deleted unneeded files");
});
Target("set-zip-directory-web-service", () =>
{
ZipDirectory(WebServiceInstaller.PublishDirectory, WebServiceInstaller.ArtifactsFile);
Console.WriteLine("Created the web-service installer artifact");
});
Target("set-zip-directory-centron-net", () =>
{
ZipDirectory(CentronNetInstaller.PublishDirectory, CentronNetInstaller.ArtifactsFile);
Console.WriteLine("Created the c-entron.NET installer artifact");
});
Target("set-dependencies-web-service", () =>
{
var dependencies = DependencyGraph.ReadDependencyGraphs(WebService.CsProj, WebService.ConnectionManager.CsProj);
DependencyGraph.Write(dependencies, CentronPaths.DependencyGraph.CentronWebServiceArtifactsFile);
});
Target("set-dependencies-centron-net", () =>
{
var dependencies = DependencyGraph.ReadDependencyGraph(CentronNet.CsProj);
DependencyGraph.Write(dependencies, CentronPaths.DependencyGraph.CentronNetArtifactsFile);
});
Target("build-web-service-linux", ["clean", "setup-versioning"], () =>
{
RunDotNet($"publish \"{WebServiceLinux.CsProj}\" -r linux-x64 --self-contained false -c Release -f net10.0 -nodeReuse:false");
Console.WriteLine("Finished building the project");
File.Delete(WebServiceLinux.PublishedCentronHostConsoleConfigFile);
FixNLogConfig(WebServiceLinux.PublishDirectory);
Console.WriteLine("Cleaned up some files");
ZipDirectory(WebServiceLinux.PublishDirectory, WebServiceLinux.ArtifactsFile);
Console.WriteLine("Created the web-service linux artifact");
});
Target("build", ["create-nuget-packages", "build-web-service", "build-centron-net"]);
Target("build-installer", ["build-web-service-installer", "build-centron-net-installer"]);
Target("update-installer-product-heat", ["build"], () =>
{
// If you're adding new DLLs or Open-Source Nuget Packages,
// don't forget to also update the file ThirdPartySoftware.cs
WXSHelper.UpdateCentronSetupProductHeat();
WXSHelper.UpdateWebServiceSetupProductHeat();
});
Target("end-to-end-tests", ["setup-versioning"], () =>
{
// Separate "dotnet build" call, so we can differentiate between build errors, and test errors
RunDotNet($"build \"{EndToEndTests.CsProj}\" -c Release -f net10.0 -nodeReuse:false");
Console.WriteLine("Finished building the EndToEnd Tests project");
RunDotNetTest($"\"{EndToEndTests.CsProj}\" -c Release -f net10.0 --no-build --logger \"trx;LogFileName={EndToEndTests.ArtifactsFileName}\" --results-directory \"{EndToEndTests.ArtifactsDirectory}\" -nodeReuse:false", timeout: TimeSpan.FromMinutes(120));
Console.WriteLine("Executed the EndToEnd Tests");
});
Target("end-to-end-tests-only", () =>
{
// Separate "dotnet build" call, so we can differentiate between build errors, and test errors
RunDotNet($"build \"{EndToEndTests.CsProj}\" -c Release -f net10.0 -nodeReuse:false");
Console.WriteLine("Finished building the EndToEnd Tests project");
RunDotNetTest($"\"{EndToEndTests.CsProj}\" -c Release -f net10.0 --no-build --logger \"trx;LogFileName={EndToEndTests.ArtifactsFileName}\" --results-directory \"{EndToEndTests.ArtifactsDirectory}\" -nodeReuse:false", timeout: TimeSpan.FromMinutes(120));
Console.WriteLine("Executed the EndToEnd Tests");
});
Target("default", ["create-nuget-packages", "build-installer"]);
await RunTargetsAndExitAsync(args);
}
}
}
@@ -0,0 +1,158 @@
using System;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Text.RegularExpressions;
using System.Threading;
using CliWrap;
using CliWrap.Buffered;
namespace Centron.Scripts
{
public static class RunHelper
{
private static string _msBuildPath;
private static string MsBuildPath
{
get
{
if (_msBuildPath == null)
FillPaths();
return _msBuildPath;
}
}
private static void FillPaths()
{
var vsWhere = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ProgramFilesX86), "Microsoft Visual Studio", "Installer", "vswhere.exe");
var output = Read(vsWhere, "-products * -requires Microsoft.Component.MSBuild -property installationPath -prerelease");
var paths = output.Split(Environment.NewLine.ToCharArray(), StringSplitOptions.RemoveEmptyEntries);
if (paths.Length == 0)
throw new Exception("Visual Studio with MSBuild component not found. Please complete the Visual Studio installation and ensure the 'Desktop development with C++' or '.NET desktop development' workload is installed.");
var path = paths.First();
var msbuildPath = Path.Combine(path, "MSBuild", "Current", "Bin", "MSBuild.exe");
if (File.Exists(msbuildPath) == false)
throw new Exception($"msbuild.exe not found at {msbuildPath}. Is Visual Studio properly installed?");
_msBuildPath = msbuildPath;
}
public static void RunDotNet(string arguments, TimeSpan? timeout = null)
{
Run("dotnet", arguments, timeout: timeout);
}
public static void RunDotNetTest(string arguments, TimeSpan? timeout = null)
{
try
{
Run("dotnet", "test " + arguments, timeout: timeout);
}
catch (Exception e) when (e.Data.Contains("ExitCode") && (int)e.Data["ExitCode"] == 1)
{
//The test run failed, but we don't want to error
//Instead return and allow the rest of the process to read the TestResults file
}
}
public static void RunMsBuild(string arguments, TimeSpan? timeout = null)
{
try
{
Run(MsBuildPath, arguments, timeout: timeout);
}
catch (Exception e) when (e.Data.Contains("ExitCode") && EnvironmentHelper.RunningInAzurePipelines())
{
ParseAndOutputDiagnosticsForAzurePipelines(e.Message);
throw new Exception($"MSBuild.exe failed with command: {arguments}.", e);
}
}
private static void ParseAndOutputDiagnosticsForAzurePipelines(string message)
{
string GetValue(Match match, string name) => match.Groups.Cast<Group>().First(f => f.Name == name).Value.Replace("[", string.Empty).Replace("]", string.Empty);
var diagnosticsRegex = new Regex(@"^\s*[\d>]*(?<FileName>.*)\((?<LineNumber>\d*),(?<ColumnNumber>\d*)\): (?<Type>error|warning) (?<Code>.*): (?<Message>.*)\[.*$", RegexOptions.Multiline);
var diagnostics = diagnosticsRegex
.Matches(message)
.Cast<Match>()
.Select(f => new
{
Type = GetValue(f, "Type"),
FileName = GetValue(f, "FileName"),
LineNumber = GetValue(f, "LineNumber"),
ColumnNumber = GetValue(f, "ColumnNumber"),
Code = GetValue(f, "Code"),
Message = GetValue(f, "Message"),
})
.Where(f => f.Type == "error") //For now, only output errors
.Distinct()
.ToList();
foreach (var diagnostic in diagnostics)
{
Console.WriteLine($"##vso[task.logissue type=error;sourcepath={diagnostic.FileName};linenumber={diagnostic.LineNumber};columnnumber={diagnostic.ColumnNumber};code={diagnostic.Code};]{diagnostic.Message}");
}
}
public static void RunSignTool(/*string certificate, string password, */string timeServer, string description, params string[] files)
{
var possibleSignToolPaths = new[]
{
@"C:\Program Files (x86)\Windows Kits\8.1\bin\x86\signtool.exe",
@"C:\Program Files (x86)\Windows Kits\10\bin\x86\signtool.exe",
@"C:\Program Files (x86)\Microsoft SDKs\ClickOnce\SignTool\signtool.exe",
};
var signToolToUse = possibleSignToolPaths.FirstOrDefault(File.Exists);
if (signToolToUse == null)
throw new Exception("signtool.exe not found");
string arguments = $"sign /fd sha384 /tr \"{timeServer}\" /td sha384 /d \"{description}\" {string.Join(" ", files.Select(f => "\"" + f + "\""))} ";
// string arguments = $"sign /f \"{certificate}\" /p \"{password}\" /fd sha256 /tr \"{timeServer}\" /td sha256 /d \"{description}\" {string.Join(" ", files.Select(f => "\"" + f + "\""))} ";
Run(signToolToUse, arguments);
}
public static string ReadDotNetTool(string tool, string arguments, string workingDirectory = null, TimeSpan? timeout = null)
{
return Read(Path.Combine(CentronPaths.DotNetToolsDirectory, tool), arguments, workingDirectory, timeout);
}
public static void Run(string name, string args, string workingDirectory = null, TimeSpan? timeout = null)
{
Read(name, args, workingDirectory, timeout);
}
public static string Read(string name, string args, string workingDirectory = null, TimeSpan? timeout = null)
{
var tokenSource = new CancellationTokenSource();
tokenSource.CancelAfter(timeout ?? GetDefaultTimeout());
var result = Cli.Wrap(name)
.WithArguments(args)
.WithWorkingDirectory(workingDirectory ?? Directory.GetCurrentDirectory())
.WithValidation(CommandResultValidation.None)
.ExecuteBufferedAsync(tokenSource.Token)
.Task.Result;
if (result.ExitCode != 0)
throw new Exception($"The command \"{name} {args}\" failed!{Environment.NewLine}{result.StandardOutput}{Environment.NewLine}{result.StandardError}")
{
Data =
{
["ExitCode"] = result.ExitCode
}
};
return result.StandardOutput + Environment.NewLine + result.StandardError;
}
private static TimeSpan GetDefaultTimeout()
{
return EnvironmentHelper.UseLongTimeout()
? TimeSpan.FromHours(2) // Long-Timeout is typically used when running a security scan during build (like GitHub Advanced Security for Azure DevOps)
: TimeSpan.FromMinutes(20);
}
}
}
@@ -0,0 +1,26 @@
using static Centron.Scripts.RunHelper;
using static Centron.Scripts.EnvironmentHelper;
namespace Centron.Scripts
{
public static class SignHelper
{
public static bool SignFiles(string description, string codeSigningCertificateEnvironmentVariable, string codeSigningCertificatePasswordEnvironmentVariable, params string[] files)
{
// var certificateFilePath = GetEnvironmentVariable(codeSigningCertificateEnvironmentVariable);
// var certificatePassword = GetEnvironmentVariable(codeSigningCertificatePasswordEnvironmentVariable);
//
// if (string.IsNullOrWhiteSpace(certificateFilePath) ||
// string.IsNullOrWhiteSpace(certificatePassword))
// {
// return false;
// }
var timeServer = "http://timestamp.digicert.com";
RunSignTool(/*certificateFilePath, certificatePassword, */timeServer, description, files);
return true;
}
}
}
@@ -0,0 +1,259 @@
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);
}
}
}
}